Author: bombe
Date: 2006-08-09 19:01:12 +0000 (Wed, 09 Aug 2006)
New Revision: 10005

Modified:
   trunk/freenet/src/freenet/clients/http/ConfigToadlet.java
   trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
   trunk/freenet/src/freenet/clients/http/FProxyToadlet.java
   trunk/freenet/src/freenet/clients/http/LocalFileInsertToadlet.java
   trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
   trunk/freenet/src/freenet/clients/http/NinjaSpider.java
   trunk/freenet/src/freenet/clients/http/PageMaker.java
   trunk/freenet/src/freenet/clients/http/PluginToadlet.java
   trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
   trunk/freenet/src/freenet/clients/http/QueueToadlet.java
   trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
   trunk/freenet/src/freenet/clients/http/Spider.java
   trunk/freenet/src/freenet/clients/http/Toadlet.java
   trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
   trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java
   trunk/freenet/src/freenet/clients/http/filter/CSSReadFilter.java
   trunk/freenet/src/freenet/clients/http/filter/DataFilterException.java
   trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java
   
trunk/freenet/src/freenet/clients/http/filter/KnownUnsafeContentTypeException.java
   
trunk/freenet/src/freenet/clients/http/filter/UnknownContentTypeException.java
   trunk/freenet/src/freenet/clients/http/filter/UnsafeContentTypeException.java
   trunk/freenet/src/freenet/node/Node.java
   trunk/freenet/src/freenet/node/useralerts/BuildOldAgeUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/ExtOldAgeUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/IPUndetectedUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/MeaningfulNodeNameUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/N2NTMUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/RevocationKeyFoundUserAlert.java
   
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
   trunk/freenet/src/freenet/node/useralerts/UserAlert.java
   trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java
   trunk/freenet/src/freenet/support/HTMLNode.java
Log:
replace stringbuffer based page-generation by html/node-based generator
use a single PageMaker instance in the toadlet server
refactor all toadlets (and some plugins) to use html-generator
modify user alerts (added getHTMLText method that returns a HTMLNode)
remove some hard-coded HTML

Modified: trunk/freenet/src/freenet/clients/http/ConfigToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ConfigToadlet.java   2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/ConfigToadlet.java   2006-08-09 
19:01:12 UTC (rev 10005)
@@ -9,7 +9,7 @@
 import freenet.config.Option;
 import freenet.config.SubConfig;
 import freenet.node.Node;
-import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -80,119 +80,89 @@
                }
                config.store();

-               StringBuffer outbuf = new StringBuffer();
+               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Configuration Applied");
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);

-               ctx.getPageMaker().makeHead(outbuf, "Configuration Applied");
-               
                if (errbuf.length() == 0) {
-                       outbuf.append("<div class=\"infobox 
infobox-success\">\n");
-                       outbuf.append("<div class=\"infobox-header\">\n");
-                       outbuf.append("Configuration Applied\n");
-                       outbuf.append("</div>\n");
-                       outbuf.append("<div class=\"infobox-content\">\n");
-                       outbuf.append("Your configuration changes were applied 
successfully<br />\n");
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-success", 
"Configuration Applied"));
+                       
ctx.getPageMaker().getContentNode(infobox).addChild("#", "Your configuration 
changes were applied successfully.");
                } else {
-                       outbuf.append("<div class=\"infobox 
infobox-error\">\n");
-                       outbuf.append("<div class=\"infobox-header\">\n");
-                       outbuf.append("Configuration Could Not Be Applied\n");
-                       outbuf.append("</div>\n");
-                       outbuf.append("<div class=\"infobox-content\">\n");
-                       outbuf.append("Your configuration changes were applied 
with the following exceptions:<br />\n");
-                       outbuf.append(HTMLEncoder.encode(errbuf.toString()));
-                       outbuf.append("<br />\n");
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-error", 
"Configuration Not Applied"));
+                       HTMLNode content = infobox.addChild("div", "class", 
"infobox-content");
+                       content.addChild("#", "Your configuration changes were 
applied with the following exceptions:");
+                       content.addChild("br");
+                       content.addChild("#", errbuf.toString());
                }

-               outbuf.append("<a href=\".\" title=\"Configuration\">Return to 
Node Configuration</a><br />\n");
-               outbuf.append("<a href=\"/\" title=\"Node 
Homepage\">Homepage</a>\n");
+               HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-normal", "Your 
Possibilities"));
+               HTMLNode content = ctx.getPageMaker().getContentNode(infobox);
+               content.addChild("a", new String[]{"href", "title"}, new 
String[]{".", "Configuration"}, "Return to node configuration");
+               content.addChild("br");
+               content.addChild("a", new String[]{"href", "title"}, new 
String[]{"/", "Node homepage"}, "Return to node homepage");
+
+               writeReply(ctx, 200, "text/html", "OK", pageNode.generate());

-               outbuf.append("</div>\n");
-               outbuf.append("</div>\n");
-               
-               ctx.getPageMaker().makeTail(outbuf);
-               writeReply(ctx, 200, "text/html", "OK", outbuf.toString());
-               
        }

        public void handleGet(URI uri, ToadletContext ctx) throws 
ToadletContextClosedException, IOException {
-               StringBuffer buf = new StringBuffer(1024);
                SubConfig[] sc = config.getConfigs();
                boolean advancedEnabled = 
node.getToadletContainer().isAdvancedDarknetEnabled();

-               ctx.getPageMaker().makeHead(buf, "Freenet Node Configuration of 
"+node.getMyName());
+               HTMLNode pageNode = ctx.getPageMaker().getPageNode("Freenet 
Node Configuration of " + node.getMyName());
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);

-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Freenet Node Configuration\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append("<form method=\"post\" action=\".\">");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\""+node.formPassword+"\">");
+               HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-normal");
+               infobox.addChild("div", "class", "infobox-header", "Freenet 
node configuration");
+               HTMLNode configNode = infobox.addChild("div", "class", 
"infobox-content");
+               HTMLNode formNode = configNode.addChild("form", new String[] { 
"action", "method" }, new String[] { ".", "post" });
+               formNode.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "formPassword", node.formPassword });

                for(int i=0; i<sc.length;i++){
-                       StringBuffer buf2 = new StringBuffer();
                        short displayedConfigElements = 0;

                        Option[] o = sc[i].getOptions();
-                       buf2.append("<ul class=\"config\"><span 
class=\"configprefix\">"+sc[i].getPrefix()+"</span>\n");
+                       HTMLNode configGroupUlNode = new HTMLNode("ul", 
"class", "config");

                        for(int j=0; j<o.length; j++){
                                if(! (!advancedEnabled && o[j].isExpert())){
                                        displayedConfigElements++;
                                        String configName = o[j].getName();

-                                       buf2.append("<li>");
-                                       buf2.append("<span 
class=\"configshortdesc\">");
-                                       buf2.append(o[j].getShortDesc());
-                                       buf2.append("</span>"); 
-                                       buf2.append("<span class=\"config\">");
+                                       HTMLNode configItemNode = 
configGroupUlNode.addChild("li");
+                                       configItemNode.addChild("span", 
"class", "configshortdesc", o[j].getShortDesc());
+                                       HTMLNode configItemValueNode = 
configItemNode.addChild("span", "class", "config");

                                        if(o[j].getValueString().equals("true") 
|| o[j].getValueString().equals("false")){
-                                               buf2.append("<select 
name=\""+sc[i].getPrefix()+"."+configName+"\" >");
+                                               HTMLNode selectNode = 
configItemValueNode.addChild("select", "name", sc[i].getPrefix() + "." + 
configName);
                                                
if(o[j].getValueString().equals("true")){
-                                                       buf2.append("<option 
value=\"true\" selected>true</option>");
-                                                       buf2.append("<option 
value=\"false\">false</option>");
+                                                       
selectNode.addChild("option", new String[] { "value", "selected" }, new 
String[] { "true", "selected" }, "true");
+                                                       
selectNode.addChild("option", "value", "false", "false");
                                                }else{
-                                                       buf2.append("<option 
value=\"true\">true</option>");
-                                                       buf2.append("<option 
value=\"false\" selected>false</option>");
+                                                       
selectNode.addChild("option", "value", "true", "true");
+                                                       
selectNode.addChild("option", new String[] { "value", "selected" }, new 
String[] { "false", "selected" }, "false");
                                                }
-                                               buf2.append("</select>");
                                        }else{
-                                               buf2.append("<input 
alt=\""+o[j].getShortDesc()+"\" class=\"config\"" +
-                                                               " type=\"text\" 
name=\""+sc[i].getPrefix()+"."+configName+"\" 
value=\""+HTMLEncoder.encode(o[j].getValueString())+"\" />");                   
          
+                                               
configItemValueNode.addChild("input", new String[] { "type", "class", "alt", 
"name", "value" }, new String[] { "text", "config", o[j].getShortDesc(), 
sc[i].getPrefix() + "." + configName, o[j].getValueString() });
                                        }
-                                       buf2.append("</span>");
-                                       buf2.append("<span 
class=\"configlongdesc\">");
-                                       buf2.append(o[j].getLongDesc());
-                                       buf2.append("</span>");
-                                       
-                                       buf2.append("</li>\n");
+                                       configItemNode.addChild("span", 
"class", "configlongdesc", o[j].getLongDesc());
                                }
                        }
-                       buf2.append("</ul>\n");

-                       if(displayedConfigElements>0)
-                               buf.append(buf2);
+                       if(displayedConfigElements>0) {
+                               formNode.addChild("div", "class", 
"configprefix", sc[i].getPrefix());
+                               formNode.addChild(configGroupUlNode);
+                       }
                }

-               buf.append("<input type=\"submit\" value=\"Apply\" />");
-               buf.append("<input type=\"reset\" value=\"Reset\" />");
-               buf.append("</form>");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               formNode.addChild("input", new String[] { "type", "value" }, 
new String[] { "submit", "Apply" });
+               formNode.addChild("input", new String[] { "type", "value" }, 
new String[] { "reset", "Reset" });

-               ctx.getPageMaker().makeTail(buf);
-               
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
        }

-       public void handlePut(URI uri, ToadletContext ctx) throws 
ToadletContextClosedException, IOException {
-               StringBuffer buf = new StringBuffer();
-               buf.append("ok!\n");
-               buf.append(uri);
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
-       }
-       
        public String supportedMethods() {
-               return "GET, PUT";
+               return "GET, POST";
        }
 }

Modified: trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java       
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java       
2006-08-09 19:01:12 UTC (rev 10005)
@@ -8,9 +8,14 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.text.DecimalFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
-import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;

 import freenet.client.HighLevelSimpleClient;
 import freenet.io.comm.PeerParseException;
@@ -18,6 +23,7 @@
 import freenet.node.Node;
 import freenet.node.PeerNode;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.MultiValueTable;
 import freenet.support.SimpleFieldSet;
 import freenet.support.io.Bucket;
@@ -78,15 +84,24 @@
                        this.writeReply(ctx, 200, "text/plain", "OK", 
sw.toString());
                        return;
                }
-               StringBuffer buf = new StringBuffer(1024);

-               //HTTPRequest request = new HTTPRequest(uri);
-               
                final boolean advancedEnabled = 
node.getToadletContainer().isAdvancedDarknetEnabled();

                /* gather connection statistics */
                PeerNode[] peerNodes = node.getDarknetConnections();

+               Arrays.sort(peerNodes, new Comparator() {
+                       public int compare(Object first, Object second) {
+                               PeerNode firstNode = (PeerNode) first;
+                               PeerNode secondNode = (PeerNode) second;
+                               int statusDifference = 
firstNode.getPeerNodeStatus() - secondNode.getPeerNodeStatus();
+                               if (statusDifference != 0) {
+                                       return statusDifference;
+                               }
+                               return 
firstNode.getName().compareTo(secondNode.getName());
+                       }
+               });
+               
                /* copy peer node statuses for consistent display. */
                int[] peerNodeStatuses = new int[peerNodes.length];
                for (int peerIndex = 0, peerCount = peerNodes.length; peerIndex 
< peerCount; peerIndex++) {
@@ -113,42 +128,42 @@
                        titleCountString = 
String.valueOf(numberOfSimpleConnected);
                }

-               String pageTitle = titleCountString + " Darknet Peers of 
"+node.getMyName();
+               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode(titleCountString + " Darknet Peers of " + 
node.getMyName());
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);

-               ctx.getPageMaker().makeHead(buf, pageTitle);
-               
                // FIXME! We need some nice images
                long now = System.currentTimeMillis();

-               node.alerts.toSummaryHtml(buf);
+               contentNode.addChild(node.alerts.createSummary());

                /* node status values */
                int bwlimitDelayTime = (int) node.getBwlimitDelayTime();
                int nodeAveragePingTime = (int) node.getNodeAveragePingTime();
-               int networkSizeEstimate = (int) node.getNetworkSizeEstimate( 0 
);
+               int networkSizeEstimate = node.getNetworkSizeEstimate(0);
                DecimalFormat fix4 = new DecimalFormat("0.0000");
                double missRoutingDistance =  
node.missRoutingDistance.currentValue();
                DecimalFormat fix1 = new DecimalFormat("##0.0%");
                double backedoffPercent =  node.backedoffPercent.currentValue();
                String nodeUptimeString = timeIntervalToString(( now - 
node.startupTime ) / 1000);

-               buf.append("<table class=\"column\"><tr><td class=\"first\">");
+               // BEGIN OVERVIEW TABLE
+               HTMLNode overviewTable = contentNode.addChild("table", "class", 
"column");
+               HTMLNode overviewTableRow = overviewTable.addChild("tr");
+               HTMLNode nextTableCell = overviewTableRow.addChild("td", 
"class", "first");

                /* node status overview box */
                if(advancedEnabled) {
-                       buf.append("<div class=\"infobox\">");
-                       buf.append("<div class=\"infobox-header\">Node status 
overview</div>");
-                       buf.append("<div class=\"infobox-content\">");
-                       buf.append("<ul>");
-                       
buf.append("<li>bwlimitDelayTime:&nbsp;").append(bwlimitDelayTime).append("ms</li>");
-                       
buf.append("<li>nodeAveragePingTime:&nbsp;").append(nodeAveragePingTime).append("ms</li>");
-                       
buf.append("<li>networkSizeEstimate:&nbsp;").append(networkSizeEstimate).append("&nbsp;nodes</li>");
-                       
buf.append("<li>nodeUptime:&nbsp;").append(nodeUptimeString).append("</li>");
-                       
buf.append("<li>missRoutingDistance:&nbsp;").append(fix4.format(missRoutingDistance)).append("</li>");
-                       
buf.append("<li>backedoffPercent:&nbsp;").append(fix1.format(backedoffPercent)).append("</li>");
-                       buf.append("</ul></div>");
-                       buf.append("</div>\n");
-                       buf.append("</td><td>");
+                       HTMLNode overviewInfobox = 
nextTableCell.addChild("div", "class", "infobox");
+                       overviewInfobox.addChild("div", "class", 
"infobox-header", "Node status overview");
+                       HTMLNode overviewInfoboxContent = 
overviewInfobox.addChild("div", "class", "infobox-content");
+                       HTMLNode overviewList = 
overviewInfoboxContent.addChild("ul");
+                       overviewList.addChild("li", "bwlimitDelayTime:\u00a0" + 
bwlimitDelayTime + "ms");
+                       overviewList.addChild("li", 
"nodeAveragePingTime:\u00a0" + nodeAveragePingTime + "ms");
+                       overviewList.addChild("li", 
"networkSizeEstimate:\u00a0" + networkSizeEstimate + "\u00a0nodes");
+                       overviewList.addChild("li", "nodeUptime:\u00a0" + 
nodeUptimeString);
+                       overviewList.addChild("li", 
"missRoutingDistance:\u00a0" + fix4.format(missRoutingDistance));
+                       overviewList.addChild("li", "backedoffPercent:\u00a0" + 
fix1.format(backedoffPercent));
+                       nextTableCell = overviewTableRow.addChild("td");
                }

                // Activity box
@@ -157,340 +172,276 @@
                int numTransferringRequests = node.getNumTransferringRequests();
                int numARKFetchers = node.getNumARKFetchers();

-               buf.append("<div class=\"infobox\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Current Activity\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
+               HTMLNode activityInfobox = nextTableCell.addChild("div", 
"class", "infobox");
+               activityInfobox.addChild("div", "class", "infobox-header", 
"Current activity");
+               HTMLNode activityInfoboxContent = 
activityInfobox.addChild("div", "class", "infobox-content");
                if ((numInserts == 0) && (numRequests == 0) && 
(numTransferringRequests == 0) && (numARKFetchers == 0)) {
-                       buf.append("Your node is not processing any requests 
right now.");
+                       activityInfoboxContent.addChild("#", "Your node is not 
processing any requests right now.");
                } else {
-                       buf.append("<ul id=\"activity\">\n");
+                       HTMLNode activityList = 
activityInfoboxContent.addChild("ul", "id", "activity");
                        if (numInserts > 0) {
-                               
buf.append("<li>Inserts:&nbsp;").append(numInserts).append("</li>");
+                               activityList.addChild("li", "Inserts:\u00a0" + 
numInserts);
                        }
                        if (numRequests > 0) {
-                               
buf.append("<li>Requests:&nbsp;").append(numRequests).append("</li>");
+                               activityList.addChild("li", "Requests:\u00a0" + 
numRequests);
                        }
                        if (numTransferringRequests > 0) {
-                               
buf.append("<li>Transferring&nbsp;Requests:&nbsp;").append(numTransferringRequests).append("</li>");
+                               activityList.addChild("li", 
"Transferring\u00a0Requests:\u00a0" + numTransferringRequests);
                        }
-                       if(advancedEnabled) {
+                       if (advancedEnabled) {
                                if (numARKFetchers > 0) {
-                                       
buf.append("<li>ARK&nbsp;Fetch&nbsp;Requests:&nbsp;").append(numARKFetchers).append("</li>");
+                                       activityList.addChild("li", 
"ARK\u00a0Fetch\u00a0Requests:\u00a0" + numARKFetchers);
                                }
                        }
-                       buf.append("</ul>\n");
                }
-               buf.append("</div>\n");
-               buf.append("</div>\n");

-               buf.append("</td><td>");
+               nextTableCell = advancedEnabled ? 
overviewTableRow.addChild("td") : overviewTableRow.addChild("td", "class", 
"last");

                // Peer statistics box
-               buf.append("<div class=\"infobox\">");
-               buf.append("<div class=\"infobox-header\">Peer 
statistics</div>");
-               buf.append("<div class=\"infobox-content\">");
-               buf.append("<ul>");
+               HTMLNode peerStatsInfobox = nextTableCell.addChild("div", 
"class", "infobox");
+               peerStatsInfobox.addChild("div", "class", "infobox-header", 
"Peer statistics");
+               HTMLNode peerStatsContent = peerStatsInfobox.addChild("div", 
"class", "infobox-content");
+               HTMLNode peerStatsList = peerStatsContent.addChild("ul");
                if (numberOfConnected > 0) {
-                       buf.append("<li><span 
class=\"peer_connected\">Connected:&nbsp;").append(numberOfConnected).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_connected", "Connected:\u00a0" + numberOfConnected);
                }
                if (numberOfRoutingBackedOff > 0) {
-                       String backoffName = "Busy";
-                       if(advancedEnabled) {
-                               backoffName = "Backed off";
-                       }
-                       buf.append("<li><span 
class=\"peer_backedoff\">").append(backoffName).append(":&nbsp;").append(numberOfRoutingBackedOff).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_backedoff", (advancedEnabled ? "Backed off" : "Busy") + ":\u00a0" + 
numberOfRoutingBackedOff);
                }
                if (numberOfTooNew > 0) {
-                       buf.append("<li><span class=\"peer_too_new\">Too 
new:&nbsp;").append(numberOfTooNew).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_too_new", "Too new:\u00a0" + numberOfTooNew);
                }
                if (numberOfTooOld > 0) {
-                       buf.append("<li><span class=\"peer_too_old\">Too 
old:&nbsp;").append(numberOfTooOld).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_too_old", "Too old:\u00a0" + numberOfTooOld);
                }
                if (numberOfDisconnected > 0) {
-                       buf.append("<li><span 
class=\"peer_disconnected\">Disconnected:&nbsp;").append(numberOfDisconnected).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_disconnected", "Disconnected:\u00a0" + numberOfDisconnected);
                }
                if (numberOfNeverConnected > 0) {
-                       buf.append("<li><span 
class=\"peer_never_connected\">Never 
Connected:&nbsp;").append(numberOfNeverConnected).append("</span></li>");
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_never_connected", "Never Connected:\u00a0" + numberOfNeverConnected);
                }
                if (numberOfDisabled > 0) {
-                       buf.append("<li><span 
class=\"peer_never_connected\">Disabled:&nbsp;").append(numberOfDisabled).append("</span></li>");
  // **FIXME**
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_never_connected", "Disabled:\u00a0" + numberOfDisabled); /* TODO */
                }
                if (numberOfBursting > 0) {
-                       buf.append("<li><span 
class=\"peer_never_connected\">Bursting:&nbsp;").append(numberOfBursting).append("</span></li>");
  // **FIXME**
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_never_connected", "Bursting:\u00a0" + numberOfBursting); /* TODO */
                }
                if (numberOfListening > 0) {
-                       buf.append("<li><span 
class=\"peer_never_connected\">Listening:&nbsp;").append(numberOfListening).append("</span></li>");
  // **FIXME**
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_never_connected", "Listening:\u00a0" + numberOfListening); /* TODO */
                }
                if (numberOfListenOnly > 0) {
-                       buf.append("<li><span 
class=\"peer_never_connected\">Listen 
Only:&nbsp;").append(numberOfListenOnly).append("</span></li>");  // **FIXME**
+                       peerStatsList.addChild("li").addChild("span", "class", 
"peer_never_connected", "Listen Only:\u00a0" + numberOfListenOnly); /* TODO */
                }
-               buf.append("</ul>");
-               buf.append("</div>");
-               buf.append("</div>\n");

                // Peer routing backoff reason box
                if(advancedEnabled) {
-                       buf.append("</td><td class=\"last\">");
-                       buf.append("<div class=\"infobox\">");
-                       buf.append("<div class=\"infobox-header\">Peer backoff 
reasons</div>");
-                       buf.append("<div class=\"infobox-content\">");
+                       nextTableCell = overviewTableRow.addChild("td", 
"class", "last");
+                       HTMLNode backoffReasonInfobox = 
nextTableCell.addChild("div", "class", "infobox");
+                       backoffReasonInfobox.addChild("div", "class", 
"infobox-header", "Peer backoff reasons");
+                       HTMLNode backoffReasonContent = 
backoffReasonInfobox.addChild("div", "class", "infobox-content");
                        String [] routingBackoffReasons = 
node.getPeerNodeRoutingBackoffReasons();
                        if(routingBackoffReasons.length == 0) {
-                               buf.append("Good, your node is not backed off 
from any peers!<br/>\n");
+                               backoffReasonContent.addChild("#", "Good, your 
node is not backed off from any peers!");
                        } else {
-                               buf.append("<ul>\n");
+                               HTMLNode reasonList = 
backoffReasonContent.addChild("ul");
                                for(int i=0;i<routingBackoffReasons.length;i++) 
{
                                        int reasonCount = 
node.getPeerNodeRoutingBackoffReasonSize(routingBackoffReasons[i]);
                                        if(reasonCount > 0) {
-                                               
buf.append("<li>").append(routingBackoffReasons[i]).append(":&nbsp;").append(reasonCount).append("</li>\n");
+                                               reasonList.addChild("li", 
routingBackoffReasons[i] + '\u00a0' + reasonCount);
                                        }
                                }
-                               buf.append("</ul>\n");
                        }
-                       buf.append("</div>");
-                       buf.append("</div>\n");
                }
+               // END OVERVIEW TABLE

-               buf.append("</td></tr></table>\n");
-               
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("My Peers");
-               if(advancedEnabled) {
-                       if (!path.endsWith("displaymessagetypes.html"))
-                       {
-                               buf.append(" <a 
href=\"displaymessagetypes.html\">(more detailed)</a>");
+               // BEGIN PEER TABLE
+               HTMLNode peerTableInfobox = contentNode.addChild("div", 
"class", "infobox infobox-normal");
+               HTMLNode peerTableInfoboxHeader = 
peerTableInfobox.addChild("div", "class", "infobox-header");
+               peerTableInfoboxHeader.addChild("#", "My peers");
+               if (advancedEnabled) {
+                       if (!path.endsWith("displaymessagetypes.html")) {
+                               peerTableInfoboxHeader.addChild("#", " ");
+                               peerTableInfoboxHeader.addChild("a", "href", 
"displaymessagetypes.html", "(more detailed)");
                        }
                }
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append("<form action=\".\" method=\"post\" 
enctype=\"multipart/form-data\">\n");
-               StringBuffer buf2 = new StringBuffer(1024);
-               buf2.append("<table class=\"darknet_connections\">\n");
-               buf2.append(" <tr>\n");
-               buf2.append("  <th></th>\n");
-               buf2.append("  <th>Status</th>\n");
-               buf2.append("  <th><span title=\"Click on the nodename link to 
send a N2NTM\" style=\"border-bottom:1px 
dotted;cursor:help;\">Name</span></th>\n");
-               if(advancedEnabled) {
-                       buf2.append("  <th><span title=\"Address:Port\" 
style=\"border-bottom:1px dotted;cursor:help;\">Address</span></th>\n");
-               }
-               buf2.append("  <th>Version</th>\n");
-               if(advancedEnabled) {
-                       buf2.append("  <th>Location</th>\n");
-                       buf2.append("  <th><span title=\"Temporarily 
disconnected. Other node busy? Wait time(s) remaining/total\" 
style=\"border-bottom:1px dotted;cursor:help;\">Backoff</span></th>\n");
-               }
-               buf2.append("  <th><span title=\"How long since the node 
connected or was last seen\" style=\"border-bottom:1px 
dotted;cursor:help;\">Connected&nbsp;/ Idle</span></th>\n");
-               buf2.append(" </tr>\n");
+               HTMLNode peerTableInfoboxContent = 
peerTableInfobox.addChild("div", "class", "infobox-content");

                if (peerNodes.length == 0) {
-                       buf2.append("<tr><td colspan=\"8\">Freenet can't work - 
you have not added any peers so far. <a href=\"/\">Go here</a> and read the top 
infobox to see how it's done.</td></tr>\n");
-                       buf2.append("</table>\n");
-                       //
-                       buf.append(buf2);
-               }
-               else {
+                       peerTableInfoboxContent.addChild("#", "Freenet can not 
work as you have not added any peers so far. Please go to the ");
+                       peerTableInfoboxContent.addChild("a", "href", "/", 
"node homepage");
+                       peerTableInfoboxContent.addChild("#", " and read the 
top infobox to see how it is done.");
+               } else {
+                       HTMLNode peerForm = 
peerTableInfoboxContent.addChild("form", new String[] { "action", "method", 
"enctype" }, new String[] { ".", "post", "multipart/form-data" });
+                       peerForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
+                       HTMLNode peerTable = peerForm.addChild("table", 
"class", "darknet_connection");
+                       HTMLNode peerTableHeaderRow = peerTable.addChild("tr");
+                       peerTableHeaderRow.addChild("th");
+                       peerTableHeaderRow.addChild("th", "Status");
+                       peerTableHeaderRow.addChild("th").addChild("span", new 
String[] { "title", "style" }, new String[] { "Click on the nodename link to 
send N2NTM", "border-bottom: 1px dotted; cursor: help;" }, "Name");
+                       if (advancedEnabled) {
+                               
peerTableHeaderRow.addChild("th").addChild("span", new String[] { "title", 
"style" }, new String[] { "Address:Port", "border-bottom: 1px dotted; cursor: 
help;" }, "Address");
+                       }
+                       peerTableHeaderRow.addChild("th", "Version");
+                       if (advancedEnabled) {
+                               peerTableHeaderRow.addChild("th", "Location");
+                               
peerTableHeaderRow.addChild("th").addChild("span", new String[] { "title", 
"style" }, new String[] { "Temporarily disconnected. Other node busy? Wait 
time(s) remaining/total", "border-bottom: 1px dotted; cursor: help;" }, 
"Backoff");
+                       }
+                       peerTableHeaderRow.addChild("th").addChild("span", new 
String[] { "title", "style" }, new String[] { "How long since the node 
connected or was last seen", "border-bottom: 1px dotted; cursor: help;" }, 
"Connected\u00a0/\u00a0Idle");

-                       // Create array
-                       Object[][] rows = new Object[peerNodes.length][];
-                       for(int i=0;i<peerNodes.length;i++) {
-                               PeerNode pn = peerNodes[i];
-                               long routingBackedOffUntil = 
pn.getRoutingBackedOffUntil();
-                               int backoff = 
(int)(Math.max(routingBackedOffUntil - now, 0));
-                               // Don't list the backoff as zero before it's 
actually zero
-                               if((backoff > 0) && (backoff < 1000) )
-                                       backoff = 1000;
+                       for (int peerIndex = 0, peerCount = peerNodes.length; 
peerIndex < peerCount; peerIndex++) {
+                               PeerNode peerNode = peerNodes[peerIndex];
+                               HTMLNode peerRow = peerTable.addChild("tr");

-                               // Elements must be HTML encoded.
-                               Object[] row = new Object[10];  // where [0] is 
the pn object and 9 is the node name only for sorting!
-                               rows[i] = row;
+                               // check box column
+                               peerRow.addChild("td", "class", 
"peer-marker").addChild("input", new String[] { "type", "name" }, new String[] 
{ "checkbox", "node_" + peerNode.hashCode() });

-                               Object status = new 
Integer(peerNodeStatuses[i]);
-                               long idle = pn.timeLastRoutable();
-                               if(pn.isRoutable()) {
-                                       idle = pn.timeLastConnectionCompleted();
-                               } else if(peerNodeStatuses[i] == 
Node.PEER_NODE_STATUS_NEVER_CONNECTED) {
-                                       idle = pn.getPeerAddedTime();
+                               // status column
+                               String statusString = 
peerNode.getPeerNodeStatusString();
+                               if (!advancedEnabled && 
(peerNode.getPeerNodeStatus() == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF)) {
+                                       statusString = "BUSY";
                                }
-                               String lastBackoffReasonOutputString = "";
-                               if(advancedEnabled) {
-                                       String backoffReason = 
pn.getLastBackoffReason();
-                                       if( backoffReason != null ) {
-                                               lastBackoffReasonOutputString = 
"/"+backoffReason;
-                                       } else {
-                                               lastBackoffReasonOutputString = 
"/";
-                                       }
+                               peerRow.addChild("td", "class", 
"peer-status").addChild("span", "class", 
peerNode.getPeerNodeStatusCSSClassName(), statusString + 
(peerNode.isFetchingARK() ? "*" : ""));
+                               
+                               // name column
+                               if (peerNode.isConnected() && 
(Integer.parseInt(peerNode.getSimpleVersion()) > 476)) {
+                                       peerRow.addChild("td", "class", 
"peer-name").addChild("a", "href", "/send_n2ntm/?peernode_hashcode=" + 
peerNode.hashCode(), peerNode.getName());
+                               } else {
+                                       peerRow.addChild("td", "class", 
"peer-name").addChild("#", peerNode.getName());
                                }
-                               String avgPingTimeString = "";
-                               if(advancedEnabled) {
-                                       if(pn.isConnected()) {
-                                               avgPingTimeString = " ("+(int) 
pn.averagePingTime()+"ms)";
+                               
+                               // address column
+                               if (advancedEnabled) {
+                                       String pingTime = "";
+                                       if (peerNode.isConnected()) {
+                                               pingTime = " (" + 
String.valueOf((int) peerNode.averagePingTime()) + "ms)";
                                        }
+                                       peerRow.addChild("td", "class", 
"peer-address").addChild("#", ((peerNode.getDetectedPeer() != null) ? 
(peerNode.getDetectedPeer().toString()) : ("(unknown address)")) + pingTime);
                                }
-                               String versionPrefixString = "";
-                               String versionString = "";
-                               String versionSuffixString = "";
-                               if(pn.publicInvalidVersion() || 
pn.publicReverseInvalidVersion()) {
-                                       versionPrefixString = "<span 
class=\"peer_version_problem\">";
-                                       versionSuffixString = "</span>";
-                               }
-                               String namePrefixString = "";
-                               String nameSuffixString = "";
-                               if(pn.isConnected() && 
(Integer.valueOf(pn.getSimpleVersion()).intValue() > 476)) {
-                                 namePrefixString = "<a 
href=\"/send_n2ntm/?peernode_hashcode="+pn.hashCode()+"\">";
-                                 nameSuffixString = "</a>";
-                               }

-                               if(advancedEnabled) {
-                                       versionString = 
HTMLEncoder.encode(pn.getVersion());
+                               // version column
+                               if (peerNode.publicReverseInvalidVersion()) {
+                                       peerRow.addChild("td", "class", 
"peer-version").addChild("span", "class", "peer_too_new", advancedEnabled ? 
peerNode.getVersion() : peerNode.getSimpleVersion());
                                } else {
-                                       versionString = 
HTMLEncoder.encode(pn.getSimpleVersion());
+                                       peerRow.addChild("td", "class", 
"peer-version").addChild("#", advancedEnabled ? peerNode.getVersion() : 
peerNode.getSimpleVersion());
                                }
-
-                               row[0] = pn;
-                               row[1] = "<input type=\"checkbox\" 
name=\"node_"+pn.hashCode()+"\" />";
-                               row[2] = status;
-                               row[3] = 
namePrefixString+HTMLEncoder.encode(pn.getName())+nameSuffixString;
-                               row[4] = ( pn.getDetectedPeer() != null ? 
HTMLEncoder.encode(pn.getDetectedPeer().toString()) : "(address unknown)" ) + 
avgPingTimeString;
-                               row[5] = 
versionPrefixString+versionString+versionSuffixString;
-                               row[6] = new 
Double(pn.getLocation().getValue());
-                               row[7] = 
fix1.format(pn.backedOffPercent.currentValue())+" "+backoff/1000 + "/" + 
pn.getRoutingBackoffLength()/1000+lastBackoffReasonOutputString;
-                               row[8] = idleToString(now, idle, ((Integer) 
status).intValue());
-                               row[9] = HTMLEncoder.encode(pn.getName());
-                       }
-       
-                       // Sort array
-                       Arrays.sort(rows, new MyComparator());
-                       
-                       // Convert status codes into status strings
-                       for(int i=0;i<rows.length;i++) {
-                               Object[] row = rows[i];
-                               String arkAsterisk = "";
-                               if(advancedEnabled) {
-                                       if(((PeerNode) row[0]).isFetchingARK()) 
{
-                                               arkAsterisk = "*";
-                                       }
+                               
+                               // location column
+                               if (advancedEnabled) {
+                                       peerRow.addChild("td", "class", 
"peer-location", String.valueOf(peerNode.getLocation().getValue()));
                                }
-                               String statusString = ((PeerNode) 
row[0]).getPeerNodeStatusString();
-                               if(!advancedEnabled && (((Integer) 
row[2]).intValue() == Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF)) {
-                                       statusString = "BUSY";
-                               }
-                               row[2] = "<span class=\""+((PeerNode) 
row[0]).getPeerNodeStatusCSSClassName()+"\">"+statusString+arkAsterisk+"</span>";
-                       }
-                       
-                       // Turn array into HTML
-                       for(int i=0;i<rows.length;i++) {
-                               Object[] row = rows[i];
-                               buf2.append("<tr>");
-                               for(int j=1;j<row.length;j++) {  // skip index 
0 as it's the PeerNode object
-                                       if(j == 9) { // skip index 9 as it's 
used for sorting purposes only
-                                       continue;
-                                   }
-                                       if(!advancedEnabled) {  // if not in 
advanced Darknet page output mode
-                                               if( (j == 4) || (j == 6) || (j 
== 7) ) {  // skip IP address/name, location and backoff times
-                                                       continue;
-                                               }
+                               
+                               // backoff column
+                               if (advancedEnabled) {
+                                       HTMLNode backoffCell = 
peerRow.addChild("td", "class", "peer-backoff");
+                                       backoffCell.addChild("#", 
fix1.format(peerNode.backedOffPercent.currentValue()));
+                                       int backoff = (int) 
(Math.max(peerNode.getRoutingBackedOffUntil() - now, 0));
+                                       // Don't list the backoff as zero 
before it's actually zero
+                                       if ((backoff > 0) && (backoff < 1000)) {
+                                               backoff = 1000;
                                        }
-                                       buf2.append("<td>"+row[j]+"</td>");
+                                       backoffCell.addChild("#", " " + 
String.valueOf(backoff / 1000) + "/" + 
String.valueOf(peerNode.getRoutingBackoffLength() / 1000));
+                                       backoffCell.addChild("#", 
(peerNode.getLastBackoffReason() == null) ? "" : ("/" + 
(peerNode.getLastBackoffReason())));
                                }
-                               buf2.append("</tr>\n");

-                               if (path.endsWith("displaymessagetypes.html"))
-                               {
-                                       buf2.append("<tr 
class=\"messagetypes\"><td colspan=\"8\">\n");
-                                       buf2.append("<table 
class=\"sentmessagetypes\">\n");
-                                       buf2.append("<tr><th>Sent Message 
Type</th><th>Count</th></tr>\n");
-                                       for (Enumeration 
keys=((PeerNode)row[0]).getLocalNodeSentMessagesToStatistic().keys(); 
keys.hasMoreElements(); )
-                                       {
-                                               Object curkey = 
keys.nextElement();
-                                               buf2.append("<tr><td>");
-                                               buf2.append((String)curkey);
-                                               buf2.append("</td><td>");
-                                               
buf2.append(((Long)((PeerNode)row[0]).getLocalNodeSentMessagesToStatistic().get(curkey))
 + "");
-                                               buf2.append("</td></tr>\n");
+                               // idle column
+                               long idle = peerNode.timeLastRoutable();
+                               if(peerNode.isRoutable()) {
+                                       idle = 
peerNode.timeLastConnectionCompleted();
+                               } else if(peerNodeStatuses[peerIndex] == 
Node.PEER_NODE_STATUS_NEVER_CONNECTED) {
+                                       idle = peerNode.getPeerAddedTime();
+                               }
+                               peerRow.addChild("td", "class", "peer-idle", 
idleToString(now, idle));
+                               
+                               if (path.endsWith("displaymessagetypes.html")) {
+                                       HTMLNode messageCountRow = 
peerTable.addChild("tr", "class", "message-status");
+                                       messageCountRow.addChild("td", 
"colspan", "2");
+                                       HTMLNode messageCountCell = 
messageCountRow.addChild("td", "colspan", String.valueOf(advancedEnabled ? 6 : 
3));
+                                       HTMLNode messageCountTable = 
messageCountCell.addChild("table", "class", "message-count");
+                                       HTMLNode countHeaderRow = 
messageCountTable.addChild("tr");
+                                       countHeaderRow.addChild("th", 
"Message");
+                                       countHeaderRow.addChild("th", 
"Incoming");
+                                       countHeaderRow.addChild("th", 
"Outgoing");
+                                       List messageNames = new ArrayList();
+                                       Map messageCounts = new HashMap();
+                                       for (Iterator incomingMessages = 
peerNode.getLocalNodeReceivedMessagesFromStatistic().keySet().iterator(); 
incomingMessages.hasNext(); ) {
+                                               String messageName = (String) 
incomingMessages.next();
+                                               messageNames.add(messageName);
+                                               Long messageCount = (Long) 
peerNode.getLocalNodeReceivedMessagesFromStatistic().get(messageName);
+                                               messageCounts.put(messageName, 
new Long[] { messageCount, new Long(0) });
                                        }
-                                       buf2.append("</table>\n");
-               
-                                       buf2.append("<table 
class=\"receivedmessagetypes\">\n");
-                                       buf2.append("<tr><th>Received Message 
Type</th><th>Count</th></tr>\n");
-                                       for (Enumeration 
keys=((PeerNode)row[0]).getLocalNodeReceivedMessagesFromStatistic().keys(); 
keys.hasMoreElements(); )
-                                       {
-                                               Object curkey = 
keys.nextElement();
-                                               buf2.append("<tr><td>");
-                                               buf2.append((String)curkey);
-                                               buf2.append("</td><td>");
-                                               
buf2.append(((Long)((PeerNode)row[0]).getLocalNodeReceivedMessagesFromStatistic().get(curkey))
 + "");
-                                               buf2.append("</td></tr>\n");
+                                       for (Iterator outgoingMessages = 
peerNode.getLocalNodeSentMessagesToStatistic().keySet().iterator(); 
outgoingMessages.hasNext(); ) {
+                                               String messageName = (String) 
outgoingMessages.next();
+                                               if 
(!messageNames.contains(messageName)) {
+                                                       
messageNames.add(messageName);
+                                               }
+                                               Long messageCount = (Long) 
peerNode.getLocalNodeSentMessagesToStatistic().get(messageName);
+                                               Long[] existingCounts = 
(Long[]) messageCounts.get(messageName);
+                                               if (existingCounts == null) {
+                                                       
messageCounts.put(messageName, new Long[] { new Long(0), messageCount });
+                                               } else {
+                                                       existingCounts[1] = 
messageCount;
+                                               }
                                        }
-                                       buf2.append("</table>\n");
-                                       buf2.append("</td></tr>\n");
+                                       Collections.sort(messageNames, new 
Comparator() {
+                                               public int compare(Object 
first, Object second) {
+                                                       return ((String) 
first).compareToIgnoreCase((String) second);
+                                               }
+                                       });
+                                       for (Iterator messageNamesIterator = 
messageNames.iterator(); messageNamesIterator.hasNext(); ) {
+                                               String messageName = (String) 
messageNamesIterator.next();
+                                               Long[] messageCount = (Long[]) 
messageCounts.get(messageName);
+                                               HTMLNode messageRow = 
messageCountTable.addChild("tr");
+                                               messageRow.addChild("td", 
messageName);
+                                               messageRow.addChild("td", 
"class", "right-align", String.valueOf(messageCount[0]));
+                                               messageRow.addChild("td", 
"class", "right-align", String.valueOf(messageCount[1]));
+                                       }
                                }
                        }
-                       buf2.append("</table>\n");
-                       //
-                       buf.append(buf2);
-                       //
+                       
                        if(!advancedEnabled) {
-                               buf.append("<input type=\"submit\" 
name=\"remove\" value=\"Remove selected peers\" />");
+                               peerForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "submit", "remove", "Remove selected 
peers" });
                        } else {
-                               buf.append("<select name=\"action\">\n");
-                               buf.append(" <option value=\"\">-- Select 
Action --</option>\n");
-                               buf.append(" <option value=\"enable\">Enable 
Selected Peers</option>\n");
-                               buf.append(" <option value=\"disable\">Disable 
Selected Peers</option>\n");
-                               buf.append(" <option 
value=\"set_burst_only\">On Selected Peers, Set BurstOnly</option>\n");
-                               buf.append(" <option 
value=\"clear_burst_only\">On Selected Peers, Clear BurstOnly</option>\n");
-                               buf.append(" <option 
value=\"set_listen_only\">On Selected Peers, Set ListenOnly</option>\n");
-                               buf.append(" <option 
value=\"clear_listen_only\">On Selected Peers, Clear ListenOnly</option>\n");
-                               buf.append(" <option value=\"\">-- -- 
--</option>\n");
-                               buf.append(" <option value=\"remove\">Remove 
Selected Peers</option>\n");
-                               buf.append("</select>\n");
-                               buf.append("<input type=\"submit\" 
name=\"submit\" value=\"Go\" />\n");
-                               buf.append("&nbsp;&nbsp;&nbsp;<span 
class=\"darknet_connections\">* Requesting ARK</span>\n");
+                               HTMLNode actionSelect = 
peerForm.addChild("select", "name", "action");
+                               actionSelect.addChild("option", "value", "", 
"-- Select action --");
+                               actionSelect.addChild("option", "value", 
"enable", "Enable selected peers");
+                               actionSelect.addChild("option", "value", 
"disable", "Disable selected peers");
+                               actionSelect.addChild("option", "value", 
"set_burst_only", "On selected peers, set BurstOnly");
+                               actionSelect.addChild("option", "value", 
"clear_burst_only", "On selected peers, clear BurstOnly");
+                               actionSelect.addChild("option", "value", 
"set_listen_only", "On selected peers, set ListenOnly");
+                               actionSelect.addChild("option", "value", 
"clear_listen_only", "On selected peers, clear ListenOnly");
+                               actionSelect.addChild("option", "value", "", 
"-- -- --");
+                               actionSelect.addChild("option", "value", 
"remove", "Remove selected peers");
+                               peerForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "submit", "submit", "Go" });
                        }
-                       buf.append("<input type=\"hidden\" 
name=\"formPassword\" value=\"").append(node.formPassword).append("\" />");
-                       buf.append("</form>\n");
                }
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               // END PEER TABLE

-               // new peer addition box
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Add another peer\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append("<form action=\".\" method=\"post\" 
enctype=\"multipart/form-data\">\n");
-               buf.append("Reference:<br />\n");
-               buf.append("<textarea id=\"reftext\" name=\"ref\" rows=\"8\" 
cols=\"74\"></textarea>\n");
-               buf.append("<br />\n");
-               buf.append("or URL:\n");
-               buf.append("<input id=\"refurl\" type=\"text\" name=\"url\" 
/>\n");
-               buf.append("<br />\n");
-               buf.append("or file:\n");
-               buf.append("<input id=\"reffile\" type=\"file\" 
name=\"reffile\" />\n");
-               buf.append("<br />\n");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\"").append(node.formPassword).append("\" />");
-               buf.append("<input type=\"submit\" name=\"add\" value=\"Add\" 
/>\n");
-               buf.append("</form>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               // BEGIN PEER ADDITION BOX
+               HTMLNode peerAdditionInfobox = contentNode.addChild("div", 
"class", "infobox infobox-normal");
+               peerAdditionInfobox.addChild("div", "class", "infobox-header", 
"Add another peer");
+               HTMLNode peerAdditionContent = 
peerAdditionInfobox.addChild("div", "class", "infobox-content");
+               HTMLNode peerAdditionForm = 
peerAdditionContent.addChild("form", new String[] { "action", "method", 
"enctype" }, new String[] { ".", "post", "multipart/form-data" });
+               peerAdditionForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
+               peerAdditionForm.addChild("#", "Paste the reference here:");
+               peerAdditionForm.addChild("br");
+               peerAdditionForm.addChild("textarea", new String[] { "id", 
"name", "rows", "cols" }, new String[] { "reftext", "ref", "8", "74" });
+               peerAdditionForm.addChild("br");
+               peerAdditionForm.addChild("#", "Enter the URL of the reference 
here: ");
+               peerAdditionForm.addChild("input", new String[] { "id", "type", 
"name" }, new String[] { "refurl", "text", "url" });
+               peerAdditionForm.addChild("br");
+               peerAdditionForm.addChild("#", "Choose the file containing the 
reference here: ");
+               peerAdditionForm.addChild("input", new String[] { "id", "type", 
"name" }, new String[] { "reffile", "file", "reffile" });
+               peerAdditionForm.addChild("br");
+               peerAdditionForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "add", "Add" });

                // our reference
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("<a href=\"myref.txt\">My Reference</a>\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append("<pre id=\"reference\">\n");
-               
buf.append(HTMLEncoder.encode(this.node.exportPublicFieldSet().toString()));
-               buf.append("</pre>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               HTMLNode referenceInfobox = contentNode.addChild("div", 
"class", "infobox infobox-normal");
+               referenceInfobox.addChild("div", "class", 
"infobox-header").addChild("a", "href", "myref.txt", "My reference");
+               referenceInfobox.addChild("div", "class", 
"infobox-content").addChild("pre", "id", "reference", 
node.exportPublicFieldSet().toString());

-               ctx.getPageMaker().makeTail(buf);
-               
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
        }

        public void handlePost(URI uri, Bucket data, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {
@@ -533,7 +484,7 @@
                                                ref.append( line ).append( "\n" 
);
                                        }
                                } catch (IOException e) {
-                                       this.sendErrorPage(ctx, 200, "Failed To 
Add Node", "Unable to retrieve node reference from " + 
HTMLEncoder.encode(urltext) + ".<br /> <a href=\".\">Please try again</a>.");
+                                       this.sendErrorPage(ctx, 200, "Failed To 
Add Node", "Unable to retrieve node reference from " + urltext + ". Please try 
again.");
                                        return;
                                } finally {
                                        if( in != null ){
@@ -545,7 +496,7 @@
                                // this slightly scary looking regexp chops any 
extra characters off the beginning or ends of lines and removes extra line 
breaks
                                ref = new 
StringBuffer(reftext.replaceAll(".*?((?:[\\w,\\.]+\\=[^\r\n]+?)|(?:End))[ 
\\t]*(?:\\r?\\n)+", "$1\n"));
                        } else {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Could not detect either a node reference or a URL.<br /> <a 
href=\".\">Please try again</a>.");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Could not detect either a node reference or a URL. Please try again.");
                                request.freeParts();
                                return;
                        }
@@ -558,25 +509,25 @@
                        try {
                                fs = new SimpleFieldSet(ref.toString(), true);
                        } catch (IOException e) {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text: <pre>" + 
HTMLEncoder.encode(ref.toString()) + "</pre> as a node reference: 
"+HTMLEncoder.encode(e.toString())+".<br /> <a href=\".\">Please try 
again</a>.");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text as a node reference. Please try again.");
                                return;
                        }
                        PeerNode pn;
                        try {
                                pn = new PeerNode(fs, this.node, false);
                        } catch (FSParseException e1) {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text: <pre>" + 
HTMLEncoder.encode(ref.toString()) + "</pre> as a node reference: " + 
HTMLEncoder.encode(e1.toString()) + ".<br /> Please <a href=\".\">Try 
again</a>");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text as a node reference. Please try again.");
                                return;
                        } catch (PeerParseException e1) {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text: <pre>" + 
HTMLEncoder.encode(ref.toString()) + "</pre> as a node reference: " + 
HTMLEncoder.encode(e1.toString()) + ".<br /> Please <a href=\".\">Try 
again</a>");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "Unable to parse the given text as a node reference. Please try again.");
                                return;
                        }
                        if(pn.getIdentityHash()==node.getIdentityHash()) {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "You can't add your own node to the list of remote peers.<br /> <a 
href=\".\">Return to the peers page</a>");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "You can\u2019t add your own node to the list of remote peers.");
                                return;
                        }
                        if(!this.node.addDarknetConnection(pn)) {
-                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "We already have the given reference.<br /> <a href=\".\">Return to the 
peers page</a>");
+                               this.sendErrorPage(ctx, 200, "Failed To Add 
Node", "We already have the given reference.");
                                return;
                        }

@@ -680,7 +631,7 @@
                }
        }

-       private String idleToString(long now, long idle, int peerNodeStatus) {
+       private String idleToString(long now, long idle) {
                if (idle == -1) {
                        return " ";
                }

Modified: trunk/freenet/src/freenet/clients/http/FProxyToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/FProxyToadlet.java   2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/FProxyToadlet.java   2006-08-09 
19:01:12 UTC (rev 10005)
@@ -25,7 +25,7 @@
 import freenet.node.Node;
 import freenet.node.RequestStarter;
 import freenet.support.Base64;
-import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.HexUtil;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
@@ -123,28 +123,26 @@

                long maxSize = httprequest.getLongParam("max-size", MAX_LENGTH);

-               StringBuffer buf = new StringBuffer();
                FreenetURI key;
                try {
                        key = new FreenetURI(ks);
                } catch (MalformedURLException e) {
-                       ctx.getPageMaker().makeHead(buf, "Invalid key");
-                       
-                       buf.append("<div class=\"infobox infobox-error\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Invalid key\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       
-                       buf.append("Expected a freenet key, but got 
"+HTMLEncoder.encode(ks)+"\n");             
-                       ctx.getPageMaker().makeBackLink(buf,ctx);
-                       buf.append("<br><a href=\"/\" title=\"Node 
Homepage\">Homepage</a>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       
-                       ctx.getPageMaker().makeTail(buf);
-                       
-                       this.writeReply(ctx, 400, "text/html", "Invalid key", 
buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Invalid key");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+
+                       HTMLNode errorInfobox = contentNode.addChild("div", 
"class", "infobox infobox-error");
+                       errorInfobox.addChild("div", "class", "infobox-header", 
"Invalid key");
+                       HTMLNode errorContent = errorInfobox.addChild("div", 
"class", "infobox-content");
+                       errorContent.addChild("#", "Expected a freenet key, but 
got ");
+                       errorContent.addChild("code", ks);
+                       errorContent.addChild("br");
+                       
errorContent.addChild(ctx.getPageMaker().createBackLink(ctx));
+                       errorContent.addChild("br");
+                       errorContent.addChild("a", new String[] { "href", 
"title" }, new String[] { "/", "Node homepage" }, "Homepage");
+
+                       StringBuffer pageBuffer = new StringBuffer();
+                       pageNode.generate(pageBuffer);
+                       this.writeReply(ctx, 400, "text/html", "Invalid key", 
pageBuffer.toString());
                        return;
                }
                try {
@@ -199,21 +197,32 @@
                                        writeReply(ctx, 200, typeName, "OK", 
data);
                                }
                        } catch (UnsafeContentTypeException e) {
-                               ctx.getPageMaker().makeHead(buf, "Potentially 
Dangerous Content");
-                               buf.append("<div class=\"infobox 
infobox-alert\">");
-                               buf.append("<div 
class=\"infobox-header\">").append(e.getHTMLEncodedTitle()).append("</div>");
-                               buf.append("<div class=\"infobox-content\">");
-                               buf.append(e.getExplanation());
-                               buf.append("<p>Your options are:</p><ul>\n");
-                               buf.append("<li><a 
href=\"/"+key.toString(false)+"?type=text/plain\">Click here</a> to open the 
file as plain text (this should not be dangerous, but it may be 
garbled).</li>\n");
+                               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Potentially dangerous content");
+                               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                               
+                               HTMLNode infobox = contentNode.addChild("div", 
"class", "infobox infobox-alert");
+                               infobox.addChild("div", "class", 
"infobox-header", e.getRawTitle());
+                               HTMLNode infoboxContent = 
infobox.addChild("div", "class", "infobox-content");
+                               infoboxContent.addChild(e.getHTMLExplanation());
+                               infoboxContent.addChild("p", "Your options 
are:");
+                               HTMLNode optionList = 
infoboxContent.addChild("ul");
+                               HTMLNode option = optionList.addChild("li");
+                               option.addChild("a", "href", "/" + 
key.toString(false) + "?type=text/plain", "Click here");
+                               option.addChild("#", " to open the file as 
plain text (this should not be dangerous but it may be garbled).");
                                // FIXME: is this safe? See bug #131
-                               buf.append("<li><a 
href=\"/"+key.toString(false)+"?forcedownload\">Click here</a> to force your 
browser to download the file to disk.</li>\n");
-                               buf.append("<li><a 
href=\"/"+key.toString(false)+"?force="+getForceValue(key, now)+"\">Click 
here</a> to open the file as "+HTMLEncoder.encode(typeName)+".</li>\n");
-                               buf.append("<li><a href=\"/\">Click here</a> to 
go to the FProxy home page.</li>\n");
-                               buf.append("</ul></div>");
-                               buf.append("</div>\n");
-                               ctx.getPageMaker().makeTail(buf);
-                               writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                               option = optionList.addChild("li");
+                               option.addChild("a", "href", "/" + 
key.toString(false) + "?forcedownload", "Click here");
+                               option.addChild("#", " to force your browser to 
download the file to disk.");
+                               option = optionList.addChild("li");
+                               option.addChild("a", "href", "/" + 
key.toString(false) + "?force=" + getForceValue(key, now), "Click here");
+                               option.addChild("#", " to open the file as " + 
typeName + ".");
+                               option = optionList.addChild("li");
+                               option.addChild("a", "href", "/", "Click here");
+                               option.addChild("#", " to go to the FProxy home 
page.");
+
+                               StringBuffer pageBuffer = new StringBuffer();
+                               pageNode.generate(pageBuffer);
+                               writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                        }
                } catch (FetchException e) {
                        String msg = e.getMessage();
@@ -223,82 +232,81 @@
                        } else if(e.newURI != null) {
                                this.writePermanentRedirect(ctx, msg, 
"/"+e.newURI.toString());
                        } else if(e.mode == FetchException.TOO_BIG) {
-                               ctx.getPageMaker().makeHead(buf, "Large File");
-                               buf.append("<table style=\"border: none; 
\">\n");
-                               String fnam = getFilename(e, key, 
e.getExpectedMimeType());
-                               buf.append("<tr><td><b>Filename</b></td><td>");
-                               buf.append("<a 
href=\"/"+URLEncoder.encode(key.toString(false))+"\">");
-                               buf.append(fnam);
-                               buf.append("</a>");
-                               buf.append("</td></tr>\n");
+                               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("File information");
+                               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                               
+                               HTMLNode infobox = contentNode.addChild("div", 
"class", "infobox infobox-information");
+                               infobox.addChild("div", "class", 
"infobox-header", "Large file");
+                               HTMLNode infoboxContent = 
infobox.addChild("div", "class", "infobox-content");
+                               HTMLNode fileInformationList = 
infoboxContent.addChild("ul");
+                               HTMLNode option = 
fileInformationList.addChild("li");
+                               option.addChild("#", "Filename: ");
+                               option.addChild("a", "href", "/" + 
key.toString(false), getFilename(e, key, e.getExpectedMimeType()));
+
                                boolean finalized = e.finalizedSize();
                                if(e.expectedSize > 0) {
-                                       buf.append("<tr><td><b>");
-                                       if(!finalized)
-                                               buf.append("Expected size (may 
change)");
-                                       else
-                                               buf.append("Size");
-                                       buf.append("</b></td><td>");
-                                       
buf.append(SizeUtil.formatSize(e.expectedSize));
-                                       buf.append("</td></tr>\n");
+                                       if (finalized) {
+                                               
fileInformationList.addChild("li", "Size: " + 
SizeUtil.formatSize(e.expectedSize));
+                                       } else {
+                                               
fileInformationList.addChild("li", "Size: " + 
SizeUtil.formatSize(e.expectedSize) + " (may change)");
+                                       }
+                               } else {
+                                       fileInformationList.addChild("li", 
"Size: unknown");
                                }
                                String mime = e.getExpectedMimeType();
                                if(mime != null) {
-                                       buf.append("<tr><td><b>");
-                                       if(!finalized)
-                                               buf.append("Expected MIME 
type");
-                                       else
-                                               buf.append("MIME type");
-                                       buf.append("</b></td><td>");
-                                       buf.append(mime);
-                                       buf.append(" bytes </td></tr>\n");
+                                       if (finalized) {
+                                               
fileInformationList.addChild("li", "MIME type: " + mime);
+                                       } else {
+                                               
fileInformationList.addChild("li", "Expected MIME type: " + mime);
+                                       }
+                               } else {
+                                       fileInformationList.addChild("li", 
"MIME type: unknown");
                                }
-                               // FIXME filename
-                               buf.append("</table>\n");
-                               buf.append("<br />The Freenet key you requested 
refers to a large file. Files of this size cannot generally be sent directly to 
your browser since they take too long for your Freenet node to retrieve. The 
following options are available: ");
-                               buf.append("<ul>");
-                               buf.append("<li><form method=\"get\" 
action=\"/"+key.toString(false)+"\">");
-                               buf.append("<input type=\"hidden\" 
name=\"max-size\" value=\""+e.expectedSize+"\">");
-                               buf.append("<input type=\"submit\" 
name=\"fetch\" value=\"Fetch anyway and display file in browser\">");
-                               buf.append("</form></li>\n");
-                               buf.append("<li><form method=\"post\" 
action=\"/queue/\">");
-                               buf.append("<input type=\"hidden\" name=\"key\" 
value=\""+key.toString(false)+"\">");
-                               buf.append("<input type=\"hidden\" 
name=\"return-type\" value=\"disk\">");
-                               buf.append("<input type=\"hidden\" 
name=\"persistence\" value=\"forever\">");
-                               buf.append("<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">");
-                               if(mime != null)
-                                       buf.append("<input type=\"hidden\" 
name=\"type\" value=\""+URLEncoder.encode(mime)+"\">");
-                               buf.append("<input type=\"submit\" 
name=\"download\" value=\"Download in background and store in downloads 
directory\">");
-                               buf.append("</form></li>\n");
-//                             buf.append("<li>Save it to disk at </li>");
-                               // FIXME add return-to-referring-page
-                               //buf.append("<li>Return to the referring page: 
");
-                               buf.append("<li><a href=\"/\" title=\"FProxy 
Home Page\" >Abort and return to the FProxy home page</a></li>");
-                               buf.append("</ul>");
-                               ctx.getPageMaker().makeTail(buf);
-                               writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
-                               // FIXME provide option to queue write to disk.
+                               
+                               infobox = contentNode.addChild("div", "class", 
"infobox infobox-information");
+                               infobox.addChild("div", "class", 
"infobox-header", "Explanation");
+                               infoboxContent = infobox.addChild("div", 
"class", "infobox-content");
+                               infoboxContent.addChild("#", "The Freenet key 
you requested refers to a large file. Files of this size cannot generally be 
sent directly to your browser since they take too long for your Freenet node to 
retrieve. The following options are available:");
+                               HTMLNode optionList = 
infoboxContent.addChild("ul");
+                               option = optionList.addChild("li");
+                               HTMLNode optionForm = option.addChild("form", 
new String[] { "action", "method" }, new String[] { "/" + key.toString(false), 
"get" });
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "max-size", 
String.valueOf(e.expectedSize) });
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "submit", "fetch", "Fetch anyway and 
display file in browser" });
+                               option = optionList.addChild("li");
+                               optionForm = option.addChild("form", new 
String[] { "action", "method" }, new String[] { "/queue/", "post" });
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "key", key.toString(false) 
});
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "return-type", "disk" });
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "persistence", "forever" });
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "formPassword", 
node.formPassword });
+                               if (mime != null) {
+                                       optionForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "type", mime });
+                               }
+                               optionForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "submit", "download", "Download in 
background and store in downloads directory" });
+                               optionList.addChild("li").addChild("a", new 
String[] { "href", "title" }, new String[] { "/", "FProxy home page" }, "Abort 
and return to the FProxy home page");
+
+                               StringBuffer pageBuffer = new StringBuffer();
+                               pageNode.generate(pageBuffer);
+                               writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                        } else {
                                if(e.errorCodes != null)
                                        extra = 
"<pre>"+e.errorCodes.toVerboseString()+"</pre>";
-                               
ctx.getPageMaker().makeHead(buf,FetchException.getShortMessage(e.mode));
+                               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode(FetchException.getShortMessage(e.mode));
+                               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+
+                               HTMLNode infobox = contentNode.addChild("div", 
"class", "infobox infobox-error");
+                               infobox.addChild("div", "class", 
"infobox-header", FetchException.getShortMessage(e.mode));
+                               HTMLNode infoboxContent = 
infobox.addChild("div", "class", "infobox-content");
+                               infoboxContent.addChild("#", "Error: " + msg + 
extra);
+                               infoboxContent.addChild("br");
+                               
infoboxContent.addChild(ctx.getPageMaker().createBackLink(ctx));
+                               infoboxContent.addChild("br");
+                               infoboxContent.addChild("a", new String[] { 
"href", "title" }, new String[] { "/", "Node homepage" }, "Homepage");

-                               buf.append("<div class=\"infobox 
infobox-error\">\n");
-                               buf.append("<div class=\"infobox-header\">\n");
-                               
buf.append(FetchException.getShortMessage(e.mode)+"\n");
-                               buf.append("</div>\n");
-                               buf.append("<div class=\"infobox-content\">\n");
-                               
-                               buf.append("Error: 
"+HTMLEncoder.encode(msg)+extra+"\n");               
-                               ctx.getPageMaker().makeBackLink(buf,ctx);
-                               buf.append("<br><a href=\"/\" title=\"Node 
Homepage\">Homepage</a>\n");
-                               buf.append("</div>\n");
-                               buf.append("</div>\n");
-                               
-                               ctx.getPageMaker().makeTail(buf);
-
+                               StringBuffer pageBuffer = new StringBuffer();
+                               pageNode.generate(pageBuffer);
                                this.writeReply(ctx, 500 /* close enough - 
FIXME probably should depend on status code */,
-                                               "text/html", 
FetchException.getShortMessage(e.mode), buf.toString());
+                                               "text/html", 
FetchException.getShortMessage(e.mode), pageBuffer.toString());
                        }
                } catch (Throwable t) {
                        Logger.error(this, "Caught "+t, t);
@@ -336,10 +344,10 @@
                        node.random.nextBytes(random);
                        FProxyToadlet fproxy = new FProxyToadlet(client, 
random, node);
                        node.setFProxy(fproxy);
-                       server.register(fproxy, "/", false);
+                       server.register(fproxy, "/", false, "Home", "homepage");

                        PproxyToadlet pproxy = new PproxyToadlet(client, 
node.pluginManager, node);
-                       server.register(pproxy, "/plugins/", true);
+                       server.register(pproxy, "/plugins/", true, "Plugins", 
"configure and manage plugins");

                        WelcomeToadlet welcometoadlet = new 
WelcomeToadlet(client, node, fproxyConfig);
                        server.register(welcometoadlet, "/welcome/", true);
@@ -348,7 +356,7 @@
                        server.register(pluginToadlet, "/plugin/", true);

                        ConfigToadlet configtoadlet = new ConfigToadlet(client, 
config, node);
-                       server.register(configtoadlet, "/config/", true);
+                       server.register(configtoadlet, "/config/", true, 
"Configuration", "configure your node");

                        StaticToadlet statictoadlet = new StaticToadlet(client);
                        server.register(statictoadlet, "/static/", true);
@@ -357,16 +365,16 @@
                        server.register(symlinkToadlet, "/sl/", true);

                        DarknetConnectionsToadlet darknetToadlet = new 
DarknetConnectionsToadlet(node, client);
-                       server.register(darknetToadlet, "/darknet/", true);
+                       server.register(darknetToadlet, "/darknet/", true, 
"Darknet", "manage darknet connections");

                        N2NTMToadlet n2ntmToadlet = new N2NTMToadlet(node, 
client);
                        server.register(n2ntmToadlet, "/send_n2ntm/", true);

                        QueueToadlet queueToadlet = new QueueToadlet(node, 
node.getFCPServer(), client);
-                       server.register(queueToadlet, "/queue/", true);
+                       server.register(queueToadlet, "/queue/", true, "Queue", 
"manage queued requests");

                        LocalFileInsertToadlet localFileInsertToadlet = new 
LocalFileInsertToadlet(node, client);
-                       server.register(localFileInsertToadlet, "/files/", 
true);
+                       server.register(localFileInsertToadlet, "/files/", 
true, "Insert Files", "insert files from the local disk");

                        // Now start the server.
                        server.start();

Modified: trunk/freenet/src/freenet/clients/http/LocalFileInsertToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/LocalFileInsertToadlet.java  
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/LocalFileInsertToadlet.java  
2006-08-09 19:01:12 UTC (rev 10005)
@@ -51,11 +51,11 @@
                StringBuffer pageBuffer = new StringBuffer(16384);
                PageMaker pageMaker = toadletContext.getPageMaker();

-               pageMaker.makeHead(pageBuffer, "Listing of " + 
HTMLEncoder.encode(currentPath.getAbsolutePath()));
-               node.alerts.toSummaryHtml(pageBuffer);
-
-               HTMLNode pageDiv = new HTMLNode("div", "class", "page");
-               HTMLNode infoboxDiv = pageDiv.addChild("div", "class", 
"infobox");
+               HTMLNode pageNode = pageMaker.getPageNode("Listing of " + 
currentPath.getAbsolutePath());
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+               contentNode.addChild(node.alerts.createSummary());
+               
+               HTMLNode infoboxDiv = contentNode.addChild("div", "class", 
"infobox");
                infoboxDiv.addChild("div", "class", "infobox-header", 
"Directory Listing: " + currentPath.getAbsolutePath());
                HTMLNode listingDiv = infoboxDiv.addChild("div", "class", 
"infobox-content");

@@ -134,9 +134,7 @@
                        ulNode.addChild("li", "Check that the specified path is 
readable by the user running the node.");
                }

-               pageDiv.generate(pageBuffer);
-               pageMaker.makeTail(pageBuffer);
-
+               pageNode.generate(pageBuffer);
                writeReply(toadletContext, 200, "text/html; charset=utf-8", 
"OK", pageBuffer.toString());
        }


Modified: trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java    2006-08-09 
19:01:12 UTC (rev 10005)
@@ -11,6 +11,7 @@
 import freenet.node.Node;
 import freenet.node.PeerNode;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -33,9 +34,10 @@
   public void handleGet(URI uri, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {

          HTTPRequest request = new HTTPRequest(uri, null, ctx);
-         StringBuffer buf = new StringBuffer(1024);
          if (request.isParameterSet("peernode_hashcode")) {
-                 ctx.getPageMaker().makeHead(buf, "Send Node To Node Text 
Message");
+                 HTMLNode pageNode = ctx.getPageMaker().getPageNode("Send Node 
to Node Text Message");
+                 HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                 
                  String peernode_name = null;
                  String input_hashcode_string = 
request.getParam("peernode_hashcode");
                  int input_hashcode = -1;
@@ -55,29 +57,25 @@
                          }
                  }
                  if(peernode_name == null) {
-                         buf.append("PeerNode.hashCode 
'"+input_hashcode_string+"' not found.<br /><br />\n");
-                         buf.append("<a href=\"/\" title=\"Back to Node 
Homepage\">Homepage</a>\n");
-                         buf.append("<a href=\"/darknet/\">Back to Darknet 
page</a>\n");
-                         ctx.getPageMaker().makeTail(buf);
-                         this.writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                         
contentNode.addChild(createPeerInfobox("infobox-error", "Peer not found", "The 
peer with the hash code \u201c" + input_hashcode_string + "\u201d could not be 
found."));
+                         StringBuffer pageBuffer = new StringBuffer();
+                         pageNode.generate(pageBuffer);
+                         this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                          return;
                  }

-                 buf.append("<form action=\".\" method=\"post\" 
enctype=\"multipart/form-data\">\n");
-                 buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\""+node.formPassword+"\">");
-                 buf.append("<div class=\"infobox infobox-normal\">");
-                 buf.append("<div class=\"infobox-header\">");
-                 buf.append("Sending Node To Node Text Message to 
"+HTMLEncoder.encode(peernode_name)+"\n");
-                 buf.append("</div>");
-                 buf.append("<div class=\"infobox-content\" id=\"n2nbox\">");
-                 buf.append("<input type=\"hidden\" name=\"hashcode\" 
value=\""+input_hashcode_string+"\" />\n");
-                 buf.append("<textarea id=\"n2ntmtext\" name=\"message\" 
rows=\"8\" cols=\"74\"></textarea><br />\n");
-                 buf.append("<input type=\"submit\" name=\"send\" value=\"Send 
message to "+HTMLEncoder.encode(peernode_name)+"\" />\n");
-                 buf.append("</div>");
-                 buf.append("</div>");
-                 buf.append("</form>\n");
-                 ctx.getPageMaker().makeTail(buf);
-                 this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                 HTMLNode infobox = contentNode.addChild("div", new String[] { 
"class", "id" }, new String[] { "infobox", "n2nbox" });
+                 infobox.addChild("div", "class", "infobox-header", "Send Node 
to Node Text Message");
+                 HTMLNode infoboxContent = infobox.addChild("div", "class", 
"infobox-content");
+                 HTMLNode messageForm = infoboxContent.addChild("form", new 
String[] { "action", "method", "enctype" }, new String[] { ".", "post", 
"multipart/form-data" });
+                 messageForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "formPassword", node.formPassword });
+                 messageForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "hashcode", input_hashcode_string });
+                 messageForm.addChild("textarea", new String[] { "id", "name", 
"rows", "cols" }, new String[] { "n2ntmtext", "message", "8", "74" });
+                 messageForm.addChild("br");
+                 messageForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "submit", "send", "Send message to " + peernode_name 
});
+                 StringBuffer pageBuffer = new StringBuffer();
+                 pageNode.generate(pageBuffer);
+                 this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                  return;
          }
          MultiValueTable headers = new MultiValueTable();
@@ -85,6 +83,17 @@
          ctx.sendReplyHeaders(302, "Found", headers, null, 0);
   }

+  private HTMLNode createPeerInfobox(String infoboxType, String header, String 
message) {
+         HTMLNode infobox = new HTMLNode("div", "class", "infobox " + 
infoboxType);
+         infobox.addChild("div", "class", "infobox-header", header);
+         HTMLNode infoboxContent = infobox.addChild("div", "class", 
"infobox-content");
+         infoboxContent.addChild("#", message);
+         HTMLNode list = infoboxContent.addChild("ul");
+         list.addChild("li").addChild("a", new String[] { "href", "title" }, 
new String[] { "/", "Back to node homepage" }, "Homepage");
+         list.addChild("li").addChild("a", new String[] { "href", "title" }, 
new String[] { "/darknet/", "Back to darknet connections" }, "Darknet 
connections");
+         return infobox;
+  }
+  
   public void handlePost(URI uri, Bucket data, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {
          if(data.size() > 1024*1024) {
                  this.writeReply(ctx, 400, "text/plain", "Too big", "Too much 
data, N2NTM toadlet limited to 1MB");
@@ -92,7 +101,6 @@
          }

          HTTPRequest request = new HTTPRequest(uri, data, ctx);
-         StringBuffer buf = new StringBuffer(1024);

          String pass = request.getPartAsString("formPassword", 32);
          if((pass == null) || !pass.equals(node.formPassword)) {
@@ -126,96 +134,47 @@
                          }
                  }
                  if(pn == null) {
-                         ctx.getPageMaker().makeHead(buf, "Node To Node Text 
Message Failed");
-                         buf.append("<div class=\"infobox infobox-error\">");
-                         buf.append("<div class=\"infobox-header\">");
-                         buf.append("Peer not Found");
-                         buf.append("</div>");
-                         buf.append("<div class=\"infobox-content\">");
-                         buf.append("PeerNode.hashCode 
'"+input_hashcode_string+"' not found.<br /><br />\n");
-                         buf.append("</div>");
-                         buf.append("</div>");
-                         buf.append("<a href=\"/\" title=\"Node 
Homepage\">Back to Homepage</a>\n");
-                         buf.append("<a href=\"/darknet/\">Back to Darknet 
page</a>\n");
-                         ctx.getPageMaker().makeTail(buf);
-                         this.writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                         HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node to Node Text Message failed");
+                         HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                         
contentNode.addChild(createPeerInfobox("infobox-error", "Peer not found", "The 
peer with the hash code \u201c" + input_hashcode_string + "\u201d could not be 
found."));
+                         StringBuffer pageBuffer = new StringBuffer();
+                         pageNode.generate(pageBuffer);
+                         this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                          return;
                  }
+                 HTMLNode pageNode = null;
                  try {
                          Message n2ntm = 
DMT.createNodeToNodeTextMessage(Node.N2N_TEXT_MESSAGE_TYPE_USERALERT, 
node.getMyName(), pn.getName(), message);
-                         String messageTextBuf = HTMLEncoder.encode(message);
-                         int j = messageTextBuf.length();
-                         StringBuffer messageTextBuf2 = new StringBuffer(j);
-                         for (int i = 0; i < j; i++) {
-                                 char ch = messageTextBuf.charAt(i);
-                                 if(ch == '\n')
-                                         messageTextBuf2.append("<br />");
-                                 else
-                                         messageTextBuf2.append(ch);
-                         }
                          if(pn == null) {
-                                 ctx.getPageMaker().makeHead(buf, "Node To 
Node Text Message Failed");
-                                 
-                                 buf.append("<div class=\"infobox 
infobox-error\">");
-                                 buf.append("<div class=\"infobox-header\">");
-                                 buf.append("Peer not Found");
-                                 buf.append("</div>");
-                                 buf.append("<div class=\"infobox-content\">");
-                                 buf.append("PeerNode.hashCode 
'"+request.getParam("hashcode")+"' not found.<br /><br />\n");
-                                 buf.append("</div>");
-                                 buf.append("</div>");
+                                 pageNode = 
ctx.getPageMaker().getPageNode("Node to Node Text Message failed");
+                                 HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                                 
contentNode.addChild(createPeerInfobox("infobox-error", "Peer not found", "The 
peer with the hash code \u201c" + request.getParam("hashcode") + "\u201d could 
not be found."));
                          } else if(!pn.isConnected()) {
-                                 ctx.getPageMaker().makeHead(buf, "Node To 
Node Text Message Failed");
-                                 
-                                 buf.append("<div class=\"infobox 
infobox-error\">");
-                                 buf.append("<div class=\"infobox-header\">");
-                                 buf.append("Peer not Connected");
-                                 buf.append("</div>");
-                                 buf.append("<div class=\"infobox-content\">");
-                                 buf.append("Peer 
'"+HTMLEncoder.encode(pn.getName())+"' is not connected.  Not sending N2NTM.<br 
/><br />\n");
-                                 buf.append("</div>");
-                                 buf.append("</div>");
+                                 pageNode = 
ctx.getPageMaker().getPageNode("Node to Node Text Message failed");
+                                 HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                                 
contentNode.addChild(createPeerInfobox("infobox-error", "Peer not connected", 
"The peer \u201c" + pn.getName() + " is not connected."));
                          } else if(pn.getPeerNodeStatus() == 
Node.PEER_NODE_STATUS_ROUTING_BACKED_OFF) {
-                                 ctx.getPageMaker().makeHead(buf, "Node To 
Node Text Message Succeeded");
-                                 
-                                 buf.append("<div class=\"infobox 
infobox-warning\">");
-                                 buf.append("<div class=\"infobox-header\">");
-                                 buf.append("Sent, but Peer is Backed Off");
-                                 buf.append("</div>");
-                                 buf.append("<div class=\"infobox-content\">");
-                                 buf.append("Peer 
'"+HTMLEncoder.encode(pn.getName())+"' is \"backed off\".  N2NTM receipt may be 
significantly delayed.<br /><br />\n");
-                                 
+                                 pageNode = 
ctx.getPageMaker().getPageNode("Node to Node Text Message succeeded");
+                                 HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                                 
contentNode.addChild(createPeerInfobox("infobox-warning", "Node to Node Text 
Message sent", "The message was successfully sent to \u201c" + pn.getName() + 
",\u201d but the node is backed off, so receipt may be significantly 
delayed."));
                                  usm.send(pn, n2ntm, null);
                                  Logger.normal(this, "Sent N2NTM to 
'"+pn.getName()+"': "+message);
-                                 
-                                 buf.append("Message should be on it's way:<hr 
/><br /><br />"+messageTextBuf2+"<br /><br />\n");
-                                 buf.append("</div>");
-                                 buf.append("</div>");
                          } else {
-                                 ctx.getPageMaker().makeHead(buf, "Node To 
Node Text Message Succeeded");
-                                 
-                                 buf.append("<div class=\"infobox 
infobox-success\">");
-                                 buf.append("<div class=\"infobox-header\">");
-                                 buf.append("Message Sent");
-                                 buf.append("</div>");
-                                 buf.append("<div class=\"infobox-content\">");
-                                 buf.append("Sending N2NTM to peer 
'"+HTMLEncoder.encode(pn.getName())+"'.<br /><br />\n");  
-                                 
+                                 pageNode = 
ctx.getPageMaker().getPageNode("Node to Node Text Message succeeded");
+                                 HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                                 
contentNode.addChild(createPeerInfobox("infobox-success", "Node to Node Text 
Message sent", "The message was successfully sent to \u201c" + pn.getName() + 
".\u201d"));
                                  usm.send(pn, n2ntm, null);
                                  Logger.normal(this, "Sent N2NTM to 
'"+pn.getName()+"': "+message);
-                                 
-                                 buf.append("Message should be on it's way:<hr 
/><br /><br />"+messageTextBuf2+"<br /><br />\n");
-                                 buf.append("</div>");
-                                 buf.append("</div>");
                          }
                  } catch (NotConnectedException e) {
-                         buf.append("Got NotConnectedException sending message 
to Peer '"+HTMLEncoder.encode(pn.getName())+"'.  Can't send N2NTM.<br /><br 
/>\n");
+                         pageNode = ctx.getPageMaker().getPageNode("Node to 
Node Text Message failed");
+                         HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                         
contentNode.addChild(createPeerInfobox("infobox-error", "Peer not connected", 
"Could not send the Node to Node Text Message to \u201c" + pn.getName() + 
".\u201d"));
                          Logger.error(this, "Caught NotConnectedException 
while trying to send n2ntm: "+e);
                  }
-                 buf.append("<a href=\"/\" title=\"Node Homepage\">Back to 
Homepage</a>\n");
-                 buf.append("<a href=\"/darknet/\">Back to Darknet 
page</a>\n");
-                 ctx.getPageMaker().makeTail(buf);
-                 this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                 StringBuffer pageBuffer = new StringBuffer();
+                 pageNode.generate(pageBuffer);
+                 this.writeReply(ctx, 200, "text/html", "OK", 
pageBuffer.toString());
                  return;
          }
          MultiValueTable headers = new MultiValueTable();

Modified: trunk/freenet/src/freenet/clients/http/NinjaSpider.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/NinjaSpider.java     2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/NinjaSpider.java     2006-08-09 
19:01:12 UTC (rev 10005)
@@ -47,6 +47,7 @@
 import freenet.plugin.HttpPlugin;
 import freenet.plugin.PluginManager;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -556,9 +557,9 @@
                        return;
                } else if ("list".equals(action)) {
                        String listName = request.getParam("listName", null);
-                       StringBuffer responseBuffer = new StringBuffer();

-                       pageMaker.makeHead(responseBuffer, pluginName);
+                       HTMLNode pageNode = pageMaker.getPageNode(pluginName);
+                       HTMLNode contentNode = 
pageMaker.getContentNode(pageNode);

                        /* create copies for multi-threaded use */
                        if (listName == null) {
@@ -566,31 +567,32 @@
                                List queued = new ArrayList(queuedURIList);
                                Set visited = new HashSet(visitedURIs);
                                Set failed = new HashSet(failedURIs);
-                               
responseBuffer.append(createNavbar(runningFetches.size(), queued.size(), 
visited.size(), failed.size()));
-                               responseBuffer.append(createAddBox());
-                               responseBuffer.append(createList("Running 
Fetches", "running", runningFetches.keySet(), maxShownURIs));
-                               responseBuffer.append(createList("Queued URIs", 
"queued", queued, maxShownURIs));
-                               responseBuffer.append(createList("Visited 
URIs", "visited", visited, maxShownURIs));
-                               responseBuffer.append(createList("Failed URIs", 
"failed", failed, maxShownURIs));
+                               
contentNode.addChild(createNavbar(runningFetches.size(), queued.size(), 
visited.size(), failed.size()));
+                               contentNode.addChild(createAddBox());
+                               contentNode.addChildren(createList("Running 
Fetches", "running", runningFetches.keySet(), maxShownURIs));
+                               contentNode.addChildren(createList("Queued 
URIs", "queued", queued, maxShownURIs));
+                               contentNode.addChildren(createList("Visited 
URIs", "visited", visited, maxShownURIs));
+                               contentNode.addChildren(createList("Failed 
URIs", "failed", failed, maxShownURIs));
                        } else {
-                               responseBuffer.append(createBackBox());
+                               contentNode.addChild(createBackBox());
                                if ("failed".equals(listName)) {
                                        Set failed = new HashSet(failedURIs);
-                                       
responseBuffer.append(createList("Failed URIs", "failed", failed, -1)); 
+                                       
contentNode.addChildren(createList("Failed URIs", "failed", failed, -1));       
                                } else if ("visited".equals(listName)) {
                                        Set visited = new HashSet(visitedURIs);
-                                       
responseBuffer.append(createList("Visited URIs", "visited", visited, -1));
+                                       
contentNode.addChildren(createList("Visited URIs", "visited", visited, -1));
                                } else if ("queued".equals(listName)) {
                                        List queued = new 
ArrayList(queuedURIList);
-                                       
responseBuffer.append(createList("Queued URIs", "queued", queued, -1));
+                                       
contentNode.addChildren(createList("Queued URIs", "queued", queued, -1));
                                } else if ("running".equals(listName)) {
                                        Map runningFetches = new 
HashMap(runningFetchesByURI);
-                                       
responseBuffer.append(createList("Running Fetches", "running", 
runningFetches.keySet(), -1));
+                                       
contentNode.addChildren(createList("Running Fetches", "running", 
runningFetches.keySet(), -1));
                                }
                        }
-                       pageMaker.makeTail(responseBuffer);
                        MultiValueTable responseHeaders = new MultiValueTable();
-                       byte[] responseBytes = 
responseBuffer.toString().getBytes("utf-8");
+                       StringBuffer pageBuffer = new StringBuffer();
+                       pageNode.generate(pageBuffer);
+                       byte[] responseBytes = pageBuffer.toString().getBytes();
                        context.sendReplyHeaders(200, "OK", responseHeaders, 
"text/html; charset=utf-8", responseBytes.length);
                        context.writeData(responseBytes);
                } else if ("add".equals(action)) {
@@ -604,7 +606,7 @@
                                queueURI(uri);
                                startSomeRequests();
                        } catch (MalformedURLException mue1) {
-                               sendSimpleResponse(context, "URL invalid", "The 
given URI is not valid. Please <a href=\"?action=list\">return</a> and try 
again.");
+                               sendSimpleResponse(context, "URL invalid", "The 
given URI is not valid. Please return and try again.");
                                return;
                        }
                        MultiValueTable responseHeaders = new MultiValueTable();
@@ -622,71 +624,65 @@

        private void sendSimpleResponse(ToadletContext context, String title, 
String message) throws ToadletContextClosedException, IOException {
                PageMaker pageMaker = context.getPageMaker();
-               StringBuffer outputBuffer = new StringBuffer();
-               pageMaker.makeHead(outputBuffer, title);
-               outputBuffer.append("<div class=\"infobox infobox-alert\">");
-               outputBuffer.append("<div 
class=\"infobox-header\">").append(HTMLEncoder.encode(title)).append("</div>\n");
-               outputBuffer.append("<div 
class=\"infobox-content\">").append(HTMLEncoder.encode(message)).append("</div>\n");
-               outputBuffer.append("</div>\n");
-               byte[] responseBytes = 
outputBuffer.toString().getBytes("utf-8");
+               HTMLNode pageNode = pageMaker.getPageNode(title);
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+               HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-alert");
+               infobox.addChild("div", "class", "infobox-header", title);
+               infobox.addChild("div", "class", "infobox-content", message);
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               byte[] responseBytes = pageBuffer.toString().getBytes("utf-8");
                context.sendReplyHeaders(200, "OK", new MultiValueTable(), 
"text/html; charset=utf-8", responseBytes.length);
                context.writeData(responseBytes);
        }

-       private StringBuffer createBackBox() {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-content\">Return to 
the <a href=\"?action=list\">list of all URIs</a>.</div>");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createBackBox() {
+               HTMLNode backBox = new HTMLNode("div", "class", "infobox");
+               HTMLNode backBoxContent = backBox.addChild("div", "class", 
"infobox-content");
+               backBoxContent.addChild("#", "Return to the ");
+               backBoxContent.addChild("a", "href", "?action=list", "list of 
all URIs");
+               backBoxContent.addChild("#", ".");
+               return backBox;
        }

-       private StringBuffer createAddBox() {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-header\">Add a 
URI</div>");
-               outputBuffer.append("<div class=\"infobox-content\"><form 
action=\"\" method=\"get\">");
-               outputBuffer.append("<input type=\"hidden\" name=\"action\" 
value=\"add\" />");
-               outputBuffer.append("<input type=\"text\" size=\"40\" 
name=\"key\" value=\"\" />");
-               outputBuffer.append("<input type=\"submit\" value=\"Add URI\" 
/>");
-               outputBuffer.append("</form></div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createAddBox() {
+               HTMLNode addBox = new HTMLNode("div", "class", "infobox");
+               addBox.addChild("div", "class", "infobox-header", "Add a URI");
+               HTMLNode addForm = addBox.addChild("div", "class", 
"infobox-content").addChild("form", new String[] { "action", "method" }, new 
String[] { "", "get" });
+               addForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "action", "add" });
+               addForm.addChild("input", new String[] { "type", "size", 
"name", "value" }, new String[] { "text", "40", "key", "" });
+               addForm.addChild("input", new String[] { "type", "value" }, new 
String[] { "submit", "Add URI" });
+               return addBox;
        }

-       private StringBuffer createNavbar(int running, int queued, int visited, 
int failed) {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox navbar\">");
-               outputBuffer.append("<div class=\"infobox-header\">Page 
navigation</div>");
-               outputBuffer.append("<div class=\"infobox-content\"><ul>");
-               outputBuffer.append("<li><a href=\"#running\">Running 
(").append(running).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#queued\">Queued 
(").append(queued).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#visited\">Visited 
(").append(visited).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#failed\">Failed 
(").append(failed).append(")</a></li>");
-               outputBuffer.append("</ul></div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createNavbar(int running, int queued, int visited, int 
failed) {
+               HTMLNode infobox = new HTMLNode("div", "class", "infobox 
navbar");
+               infobox.addChild("div", "class", "infobox-header", "Page 
navigation");
+               HTMLNode links = infobox.addChild("div", "class", 
"infobox-content").addChild("ul");
+               links.addChild("li").addChild("a", "href", "#running", "Running 
(" + running + ")");
+               links.addChild("li").addChild("a", "href", "#queued", "Queued 
(" + queued + ")");
+               links.addChild("li").addChild("a", "href", "#visited", "Visited 
(" + visited + ")");
+               links.addChild("li").addChild("a", "href", "#failed", "Failed 
(" + failed + ")");
+               return infobox;
        }

-       private StringBuffer createList(String listName, String anchorName, 
Collection collection, int maxCount) {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<a 
name=\"").append(HTMLEncoder.encode(anchorName)).append("\"></a>");
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div 
class=\"infobox-header\">").append(HTMLEncoder.encode(listName)).append(" 
(").append(collection.size()).append(")</div>\n");
-               outputBuffer.append("<div class=\"infobox-content\">");
+       private HTMLNode[] createList(String listName, String anchorName, 
Collection collection, int maxCount) {
+               HTMLNode listBox = new HTMLNode("div", "class", "infobox");
+               listBox.addChild("div", "class", "infobox-header", listName + " 
(" + collection.size() + ")");
+               HTMLNode listContent = listBox.addChild("div", "class", 
"infobox-content");
                Iterator collectionItems = collection.iterator();
                int itemCount = 0;
                while (collectionItems.hasNext()) {
                        FreenetURI uri = (FreenetURI) collectionItems.next();
-                       
outputBuffer.append(HTMLEncoder.encode(uri.toString())).append("<br/>\n");
+                       listContent.addChild(uri.toString());
+                       listContent.addChild("br");
                        if (itemCount++ == maxCount) {
-                               outputBuffer.append("<br/><a 
href=\"?action=list&amp;listName=").append(HTMLEncoder.encode(anchorName)).append("\">Show
 all&hellip;</a>");
+                               listContent.addChild("br");
+                               listContent.addChild("a", "href", 
"?action=list&listName=" + anchorName, "Show all\u2026");
                                break;
                        }
                }
-               outputBuffer.append("</div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+               return new HTMLNode[] { new HTMLNode("a", "name", anchorName), 
listBox };
        }

        /**

Modified: trunk/freenet/src/freenet/clients/http/PageMaker.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/PageMaker.java       2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/PageMaker.java       2006-08-09 
19:01:12 UTC (rev 10005)
@@ -8,11 +8,14 @@
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;

-import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;

 /** Simple class to output standard heads and tail for web interface pages. 
@@ -21,6 +24,10 @@

        private static final String DEFAULT_THEME = "clean";
        private String theme;
+       private final List navigationLinkTexts = new ArrayList();
+       private final Map navigationLinkTitles = new HashMap();
+       private final Map navigationLinks = new HashMap();
+       private final Map contentNodes = new HashMap();

        /** Cache for themes read from the JAR file. */
        private List jarThemesCache = null;
@@ -37,65 +44,101 @@
                }
        }

-       public void makeBackLink(StringBuffer buf, ToadletContext ctx){
-               // My browser sends it with one 'r'
-               String ref = (String)ctx.getHeaders().get("referer");
-               if(ref!=null) 
-                       buf.append("<br><a href=\""+ref+"\" title=\"Back\" 
Back</a>\n");
-               else
-                       buf.append("<br><a href=\"javascript:back()\" 
title=\"Back\">Back</a>\n");
-               
+       public void addNavigationLink(String path, String name, String title) {
+               navigationLinkTexts.add(name);
+               navigationLinkTitles.put(name, title);
+               navigationLinks.put(name, path);
        }

-       public void makeTopHead(StringBuffer buf) {
-               buf.append("<!DOCTYPE\n"
-                               + "     html PUBLIC \"-//W3C//DTD XHTML 
1.1//EN\">\n"
-                               + "<html xml:lang=\"en\">\n"
-                               + "<head>\n"
-                               + "<meta http-equiv=\"Content-Type\" 
content=\"text/html; charset=UTF-8\" />\n"
-                               + "<link rel=\"stylesheet\" 
href=\"/static/themes/"+this.theme+"/theme.css\" type=\"text/css\" />\n");
+       public void removeNavigationLink(String name) {
+               navigationLinkTexts.remove(name);
+               navigationLinkTitles.remove(name);
+               navigationLinks.remove(name);
+       }
+       
+       public HTMLNode createBackLink(ToadletContext toadletContext) {
+               String referer = (String) 
toadletContext.getHeaders().get("referer");
+               if (referer != null) {
+                       return new HTMLNode("a", new String[] { "href", "title" 
}, new String[] { referer, "Back" }, "Back");
+               }
+               return new HTMLNode("a", new String[] { "href", "title" }, new 
String[] { "javascript:back()", "Back" }, "Back");
+       }
+       
+       public HTMLNode getPageNode(String title) {
+               return getPageNode(title, true);
+       }
+
+       public HTMLNode getPageNode(String title, boolean 
renderNavigationLinks) {
+               HTMLNode pageNode = new HTMLNode.HTMLDoctype("html", 
"-//W3C//DTD XHTML 1.1//EN");
+               HTMLNode htmlNode = pageNode.addChild("html", "xml:lang", "en");
+               HTMLNode headNode = htmlNode.addChild("head");
+               headNode.addChild("title", title + " - Freenet");
+               headNode.addChild("meta", new String[] { "http-equiv", 
"content" }, new String[] { "Content-Type", "text/html; charset=utf-8" });
+               headNode.addChild("link", new String[] { "rel", "href", "type", 
"title" }, new String[] { "stylesheet", "/static/themes/" + theme + 
"/theme.css", "text/css", theme });
                List themes = getThemes();
-               for(int i=0; i<themes.size() ; i++){
-                       if(!themes.get(i).toString().equals(theme))
-                               buf.append("<link rel=\"alternate stylesheet\" 
type=\"text/css\" href=\"/static/themes/"+themes.get(i)+"/theme.css\" 
media=\"screen\" title=\""+themes.get(i)+"\" />\n");
+               for (Iterator themesIterator = themes.iterator(); 
themesIterator.hasNext();) {
+                       String themeName = (String) themesIterator.next();
+                       headNode.addChild("link", new String[] { "rel", "href", 
"type", "media", "title" }, new String[] { "alternate stylesheet", 
"/static/themes/" + themeName + "/theme.css", "text/css", "screen", themeName 
});
                }
+               HTMLNode bodyNode = htmlNode.addChild("body");
+               HTMLNode pageDiv = bodyNode.addChild("div", "id", "page");
+               HTMLNode topBarDiv = pageDiv.addChild("div", "id", "topbar");
+               topBarDiv.addChild("h1", title);
+               if (renderNavigationLinks) {
+                       HTMLNode navbarDiv = pageDiv.addChild("div", "id", 
"navbar");
+                       HTMLNode navbarUl = navbarDiv.addChild("ul", "id", 
"navlist");
+                       for (Iterator navigationLinkIterator = 
navigationLinkTexts.iterator(); navigationLinkIterator.hasNext();) {
+                               String navigationLink = (String) 
navigationLinkIterator.next();
+                               String navigationTitle = (String) 
navigationLinkTitles.get(navigationLink);
+                               String navigationPath = (String) 
navigationLinks.get(navigationLink);
+                               HTMLNode listItem = navbarUl.addChild("li");
+                               listItem.addChild("a", new String[] { "href", 
"title" }, new String[] { navigationPath, navigationTitle }, navigationLink);
+                       }
+               }
+               HTMLNode contentDiv = pageDiv.addChild("div", "id", "content");
+               contentNodes.put(pageNode, contentDiv);
+               return pageNode;
        }

-       public void makeBottomHead(StringBuffer buf, String title, boolean 
navbars) {
-               String sanitizedTitle = HTMLEncoder.encode(title);
-               buf.append("<title>"+sanitizedTitle+" - Freenet</title>\n"
-                               + "</head>\n"
-                               + "<body>\n"
-                               + "<div id=\"page\">\n"
-                               + "<div id=\"topbar\">\n"
-                               + "<h1>"+sanitizedTitle+"</h1>\n"
-                               + "</div>\n");
-               if (navbars) this.makeNavBar(buf);
-               buf.append("<div id=\"content\">\n");
+       /**
+        * Returns the content node that belongs to the specified page node.
+        * <p>
+        * <strong>Warning:</strong> this method can only be called once!
+        * 
+        * @param pageNode
+        *            The page node to get the content node for
+        * @return The content node for the specified page node
+        */
+       public HTMLNode getContentNode(HTMLNode pageNode) {
+               return (HTMLNode) contentNodes.remove(pageNode);
        }

-       public void makeBottomHead(StringBuffer buf, String title) {
-               makeBottomHead(buf, title, true);
+       public HTMLNode getInfobox(String header) {
+               return getInfobox((header != null) ? new HTMLNode("#", header) 
: (HTMLNode) null);
        }

-       public void makeHead(StringBuffer buf, String title) {
-               makeTopHead(buf);
-               makeBottomHead(buf, title);
+       public HTMLNode getInfobox(HTMLNode header) {
+               return getInfobox(null, header);
        }

-       public void makeHead(StringBuffer buf, String title, boolean navbars) {
-               makeTopHead(buf);
-               makeBottomHead(buf, title, navbars);
+       public HTMLNode getInfobox(String category, String header) {
+               return getInfobox(category, (header != null) ? new 
HTMLNode("#", header) : (HTMLNode) null);
        }

-       public void makeTail(StringBuffer buf) {
-               buf.append("<br style=\"clear: all;\"/>\n"
-                               + "</div>\n"
-                               +"</div>\n"
-                               +"</body>\n"
-                               + "</html>\n");
+       public HTMLNode getInfobox(String category, HTMLNode header) {
+               if (header == null) throw new NullPointerException();
+               HTMLNode infobox = new HTMLNode("div", "class", "infobox" + 
((category == null) ? "" : (" " + category)));
+               if (header != null) {
+                       infobox.addChild("div", "class", 
"infobox-header").addChild(header);
+               }
+               contentNodes.put(infobox, infobox.addChild("div", "class", 
"infobox-content"));
+               return infobox;
        }

+       public HTMLNode createFormPasswordInput(String formPassword) {
+               return new HTMLNode("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "formPassword", formPassword });
+       }
+       
        /**
         * Returns an {@link ArrayList} containing the names of all available
         * themes. If freenet was started from a JAR file the list is cached
@@ -153,15 +196,4 @@
                return themes;
        }

-       private void makeNavBar(StringBuffer buf) {
-               buf.append("<div id=\"navbar\">\n"
-                               + "<ul id=\"navlist\">\n"
-                               + "<li><a href=\"/\" 
title=\"Homepage\">Home</a></li>\n"
-                               + "<li><a href=\"/plugins/\" title=\"Configure 
Plugins\">Plugins</a></li>\n"
-                               + "<li><a href=\"/config/\" title=\"Configure 
your node\">Configuration</a></li>\n"
-                               + "<li><a href=\"/darknet/\" title=\"Manage 
darknet connections\">Darknet</a></li>\n"
-                               + "<li><a href=\"/queue/\" title=\"Manage 
queued requests\">Queue</a></li>\n"
-                               + "</ul>\n"
-                               + "</div>\n");
-       }
 }


Property changes on: trunk/freenet/src/freenet/clients/http/PageMaker.java
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: trunk/freenet/src/freenet/clients/http/PluginToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/PluginToadlet.java   2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/PluginToadlet.java   2006-08-09 
19:01:12 UTC (rev 10005)
@@ -12,6 +12,7 @@
 import freenet.plugin.Plugin;
 import freenet.plugin.PluginManager;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;

@@ -196,44 +197,44 @@
        private StringBuffer listPlugins(ToadletContext context) {
                Plugin[] plugins = pluginManager.getPlugins();
                PageMaker pageMaker = context.getPageMaker();
-               StringBuffer outputBuffer = new StringBuffer();
-               pageMaker.makeHead(outputBuffer, "List of Plugins", true);
+               HTMLNode pageNode = pageMaker.getPageNode("List of Plugins");
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);

-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-header\">Plugin 
list</div>");
-               outputBuffer.append("<div class=\"infobox-content\"><table 
class=\"plugintable\">");
-               outputBuffer.append("<tr>");
-               outputBuffer.append("<th>Plugin Name</th>");
-               outputBuffer.append("<th>Internal Name</th>");
-               outputBuffer.append("<th colspan=\"3\" />");
-               outputBuffer.append("</tr>\n");
+               HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox");
+               infobox.addChild("div", "class", "infobox-header", "Plugin 
list");
+               HTMLNode table = infobox.addChild("div", "class", 
"infobox-content").addChild("table", "class", "plugintable");
+               HTMLNode headerRow = table.addChild("tr");
+               headerRow.addChild("th", "Plugin Name");
+               headerRow.addChild("th", "Internal Name");
+               headerRow.addChild("th", "colspan", "3");
                for (int pluginIndex = 0, pluginCount = plugins.length; 
pluginIndex < pluginCount; pluginIndex++) {
                        Plugin plugin = plugins[pluginIndex];
                        String internalName = plugin.getClass().getName() + "@" 
+ pluginIndex;
-                       outputBuffer.append("<tr>");
-                       
outputBuffer.append("<td>").append(HTMLEncoder.encode(plugin.getPluginName())).append("</td>");
-                       
outputBuffer.append("<td>").append(HTMLEncoder.encode(internalName)).append("</td>");
+                       HTMLNode tableRow = table.addChild("tr");
+                       tableRow.addChild("td", plugin.getPluginName());
+                       tableRow.addChild("td", internalName);
                        if (plugin instanceof HttpPlugin) {
-                               outputBuffer.append("<td><form 
action=\"").append(HTMLEncoder.encode(internalName)).append("\" 
method=\"get\"><input type=\"submit\" value=\"Visit\" /></form></td>");
+                               tableRow.addChild("td").addChild("form", new 
String[] { "action", "method" }, new String[] { internalName, "get" 
}).addChild("input", new String[] { "type", "value" }, new String[] { "submit", 
"Visit" });
                        } else {
-                               outputBuffer.append("<td/>");
+                               tableRow.addChild("td");
                        }
-                       outputBuffer.append("<td><form action=\"./\" 
method=\"post\"><input type=\"hidden\" name=\"action\" value=\"reload\"/><input 
type=\"hidden\" name=\"pluginName\" value=\"").append(internalName).append("\" 
/><input type=\"submit\" value=\"Reload\" />" +
-                                       "<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                       "</form></td>");
-                       outputBuffer.append("<td><form action=\"./\" 
method=\"post\"><input type=\"hidden\" name=\"action\" value=\"unload\"/><input 
type=\"hidden\" name=\"pluginName\" value=\"").append(internalName).append("\" 
/><input type=\"submit\" value=\"Unload\" />" +
-                                       "<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                       "</form></td>");
-                       outputBuffer.append("</tr>\n");
+                       HTMLNode reloadForm = 
tableRow.addChild("td").addChild("form", new String[] { "action", "method" }, 
new String[] { ".", "post" });
+                       reloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "action", "reload" });
+                       reloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "pluginName", internalName });
+                       reloadForm.addChild("input", new String[] { "type", 
"value" }, new String[] { "submit", "Reload" });
+                       reloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
+                       HTMLNode unloadForm = 
tableRow.addChild("td").addChild("form", new String[] { "action", "method" }, 
new String[] { ".", "post" });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "action", "unload" });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "pluginName", internalName });
+                       unloadForm.addChild("input", new String[] { "type", 
"value" }, new String[] { "submit", "Unload" });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
                }
-               outputBuffer.append("</table>");
-               outputBuffer.append("</div>\n");
-               outputBuffer.append("</div>\n");

-               appendAddPluginBox(outputBuffer);
+               contentNode.addChild(createAddPluginBox());

-               pageMaker.makeTail(outputBuffer);
-               return outputBuffer;
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               return pageBuffer;
        }

        /**
@@ -250,16 +251,19 @@
         */
        private StringBuffer createBox(ToadletContext context, String title, 
String message) {
                PageMaker pageMaker = context.getPageMaker();
-               StringBuffer outputBuffer = new StringBuffer();
-               pageMaker.makeHead(outputBuffer, HTMLEncoder.encode(title));
-               outputBuffer.append("<div class=\"infobox infobox-alert\">");
-               outputBuffer.append("<div 
class=\"infobox-header\">").append(HTMLEncoder.encode(title)).append("</div>\n");
-               outputBuffer.append("<div 
class=\"infobox-content\"><p>").append(HTMLEncoder.encode(message)).append("</p>");
-               outputBuffer.append("<p>Please <a 
href=\"?action=list\">return</a> to the list of plugins.</p></div>");
-               outputBuffer.append("</div>\n");
-               pageMaker.makeTail(outputBuffer);
-
-               return outputBuffer;
+               HTMLNode pageNode = pageMaker.getPageNode(title);
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+               HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-alert");
+               infobox.addChild("div", "class", "infobox-header", title);
+               HTMLNode infoboxContent = infobox.addChild("div", "class", 
"infobox-content");
+               infoboxContent.addChild("#", message);
+               infoboxContent.addChild("br");
+               infoboxContent.addChild("#", "Please ");
+               infoboxContent.addChild("a", "href", "?action=list", "return");
+               infoboxContent.addChild("#", " to the list of plugins.");
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               return pageBuffer;
        }

        /**
@@ -269,18 +273,15 @@
         * @param outputBuffer
         *            The StringBuffer to append the HTML code to
         */
-       private void appendAddPluginBox(StringBuffer outputBuffer) {
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-header\">Add a 
plugin</div>");
-               outputBuffer.append("<div class=\"infobox-content\">");
-               outputBuffer.append("<form action=\"./\" method=\"post\">");
-               outputBuffer.append("<input type=\"hidden\" name=\"action\" 
value=\"add\" />");
-               outputBuffer.append("<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">");
-               outputBuffer.append("<input type=\"text\" size=\"40\" 
name=\"pluginName\" value=\"\" />&nbsp;");
-               outputBuffer.append("<input type=\"submit\" value=\"Load 
plugin\" />");
-               outputBuffer.append("</form>");
-               outputBuffer.append("</div>\n");
-               outputBuffer.append("</div>\n");
+       private HTMLNode createAddPluginBox() {
+               HTMLNode addPluginBox = new HTMLNode("div", "class", "infobox");
+               addPluginBox.addChild("div", "class", "infobox-header", "Add a 
plugin");
+               HTMLNode addForm = addPluginBox.addChild("div", "class", 
"infobox-content").addChild("form", new String[] { "action", "method" }, new 
String[] { ".", "post" });
+               addForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "action", "add" });
+               addForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "formPassword", node.formPassword });
+               addForm.addChild("input", new String[] { "type", "name", 
"value", "size" }, new String[] { "text", "pluginName", "", "40" });
+               addForm.addChild("input", new String[] { "type", "value" }, new 
String[] { "submit", "Load plugin" });
+               return addPluginBox;
        }

 }

Modified: trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/PproxyToadlet.java   2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/PproxyToadlet.java   2006-08-09 
19:01:12 UTC (rev 10005)
@@ -13,7 +13,7 @@
 import freenet.pluginmanager.PluginHTTPException;
 import freenet.pluginmanager.PluginInfoWrapper;
 import freenet.pluginmanager.PluginManager;
-import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -52,7 +52,6 @@
                        return;
                }

-               StringBuffer buf = new StringBuffer();
                MultiValueTable headers = new MultiValueTable();

                String pass = request.getParam("formPassword");
@@ -76,37 +75,29 @@
                        return;
                }if (request.getParam("unloadconfirm").length() > 0) {
                        pm.killPlugin(request.getParam("unloadconfirm"));
-                       ctx.getPageMaker().makeHead(buf, "Plugins");
-                       buf.append("<div class=\"infobox infobox-success\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Plugin Unloaded\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("The plugin " + 
HTMLEncoder.encode(request.getParam("remove")) + " has been unloaded.<br /><a 
href=\"/plugins/\">Return to Plugins Page</a>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                               
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Plugins");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-success");
+                       infobox.addChild("div", "class", "infobox-header", 
"Plugin unloaded");
+                       HTMLNode infoboxContent = infobox.addChild("div", 
"class", "infobox-content");
+                       infoboxContent.addChild("#", "The plugin " + 
request.getParam("remove") + " has been unloaded.");
+                       infoboxContent.addChild("br");
+                       infoboxContent.addChild("a", "href", "/plugins/", 
"Return to Plugin page.");
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                }if (request.getParam("unload").length() > 0) {
-                       ctx.getPageMaker().makeHead(buf, "Plugins");
-                       buf.append("<div class=\"infobox infobox-query\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Unload Plugin?\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("Are you sure you wish to unload " + 
HTMLEncoder.encode(request.getParam("unload")) + "?\n");
-                       buf.append("<form action=\"/plugins/\" 
method=\"post\">\n");
-                       buf.append("<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">");
-                       buf.append("<input type=\"submit\" name=\"cancel\" 
value=\"Cancel\" />\n");
-                       buf.append("<input type=\"hidden\" 
name=\"unloadconfirm\" value=\"" + 
HTMLEncoder.encode(request.getParam("unload")) + "\">\n");
-                       buf.append("<input type=\"submit\" name=\"confirm\" 
value=\"Unload\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Plugins");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-query");
+                       infobox.addChild("div", "class", "infobox-header", 
"Unload plugin?");
+                       HTMLNode infoboxContent = infobox.addChild("div", 
"class", "infobox-content");
+                       infoboxContent.addChild("#", "Are you sure you wish to 
unload " + request.getParam("unload") + "?");
+                       HTMLNode unloadForm = infoboxContent.addChild("form", 
new String[] { "action", "method" }, new String[] { "/plugins/", "post" });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "cancel", "Cancel" });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "unloadconfirm", 
request.getParam("unload") });
+                       unloadForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "confirm", "Unload" });
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                }else if (request.getParam("reload").length() > 0) {
                        String fn = null;
@@ -194,64 +185,54 @@

        private void showPluginList(ToadletContext ctx, HTTPRequest request) 
throws ToadletContextClosedException, IOException {
                if (!request.hasParameters()) {
-                       StringBuffer out = new StringBuffer();
-                       ctx.getPageMaker().makeHead(out, "Plugins of 
"+node.getMyName());
-                       //
-                       out.append("<div class=\"infobox infobox-normal\">\n");
-                       out.append("<div class=\"infobox-header\">\n");
-                       out.append("Plugin List\n");
-                       out.append("</div>\n");
-                       out.append("<div class=\"infobox-content\">\n");
-                       //
-                       out.append("<table class=\"plugins\">");
-                       out.append("<tr><th>Classname</th><th>Internal 
ID</th><th>Started at</th><th></th></tr>\n");
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Plugins of " + node.getMyName());
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       
+                       HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-normal");
+                       infobox.addChild("div", "class", "infobox-header", 
"Plugin list");
+                       HTMLNode infoboxContent = infobox.addChild("div", 
"class", "infobox-content");
+                       HTMLNode pluginTable = infoboxContent.addChild("table", 
"class", "plugins");
+                       HTMLNode headerRow = pluginTable.addChild("tr");
+                       headerRow.addChild("th", "Classname");
+                       headerRow.addChild("th", "Internal ID");
+                       headerRow.addChild("th", "Started at");
+                       headerRow.addChild("th");
+                       
                        if (pm.getPlugins().isEmpty()) {
-                               out.append("<tr><td colspan=\"4\">No plugins 
loaded</td></tr>\n");
+                               pluginTable.addChild("tr").addChild("td", 
"colspan", "4", "No plugins loaded");
                        }
                        else {
                                Iterator it = pm.getPlugins().iterator();
                                while (it.hasNext()) {
                                        PluginInfoWrapper pi = 
(PluginInfoWrapper) it.next();
-                                       out.append("<tr>");
-                                       out.append("<td>" + 
pi.getPluginClassName() + "</td>");
-                                       out.append("<td>" + pi.getThreadName() 
+ "</td>");
-                                       out.append("<td>" + (new 
Date(pi.getStarted())) + "</td>");
-                                       out.append("<td>");
+                                       HTMLNode pluginRow = 
pluginTable.addChild("tr");
+                                       pluginRow.addChild("td", 
pi.getPluginClassName());
+                                       pluginRow.addChild("td", 
pi.getThreadName());
+                                       pluginRow.addChild("td", new 
Date(pi.getStarted()).toString());
+                                       HTMLNode actionCell = 
pluginRow.addChild("td");
                                        if (pi.isPproxyPlugin()) {
-                                               out.append("<form 
method=\"get\" action=\"" + pi.getPluginClassName() + "\">" +
-                                                               "<input 
type=\"hidden\" name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                                               "<input 
type=\"submit\" value=\"Visit\"></form>");
+                                               HTMLNode visitForm = 
actionCell.addChild("form", new String[] { "method", "action" }, new String[] { 
"get", pi.getPluginClassName() });
+                                               visitForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", 
node.formPassword });
+                                               visitForm.addChild("input", new 
String[] { "type", "value" }, new String[] { "submit", "Visit" });
                                        }
-                                       out.append("<form method=\"post\" 
action=\".\">" +
-                                                       "<input type=\"hidden\" 
name=\"unload\" value=\"" + pi.getThreadName() + "\" />"+
-                                                       "<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                                       "<input type=\"submit\" 
value=\"Unload\"></form>");
-                                       out.append("<form method=\"post\" 
action=\".\">" +
-                                                       "<input type=\"hidden\" 
name=\"reload\" value=\"" + pi.getThreadName() + "\" />"+
-                                                       "<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                                       "<input type=\"submit\" 
value=\"Reload\"></form>");
-                                       out.append("</td></tr>\n");
+                                       HTMLNode unloadForm = 
actionCell.addChild("form", new String[] { "action", "method" }, new String[] { 
".", "post" });
+                                       unloadForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", 
node.formPassword });
+                                       unloadForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "unload", 
pi.getThreadName() });
+                                       unloadForm.addChild("input", new 
String[] { "type", "value" }, new String[] { "submit", "Unload" });
+                                       HTMLNode reloadForm = 
actionCell.addChild("form", new String[] { "action", "method" }, new String[] { 
".", "post" });
+                                       reloadForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", 
node.formPassword });
+                                       reloadForm.addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "hidden", "reload", 
pi.getThreadName() });
+                                       reloadForm.addChild("input", new 
String[] { "type", "value" }, new String[] { "submit", "Reload" });
                                }
                        }
-                       out.append("</table>");
-                       //String ret = "<hr/>" + out.toString();
-                       //ret = pm.dumpPlugins().replaceAll(",", "\n&nbsp; 
&nbsp; ").replaceAll("\"", " \" ");
-                       /*if (ret.length() < 6)
-                               ret += "<i>No plugins loaded</i>\n";
-                       ret += "<hr/>";*/

-                       
-                       // Obsolete
-                       //out.append("<form method=\"get\"><div>Remove plugin: 
(enter ID) <input type=\"text\" name=\"remove\" size=40/><input type=\"submit\" 
value=\"Remove\"/></div></form>\n");
-                       out.append("<form method=\"post\" action=\".\">" +
-                                       "<input type=\"hidden\" 
name=\"formPassword\" value=\""+node.formPassword+"\">"+
-                                       "<div>Load plugin: <input type=\"text\" 
name=\"load\" size=\"40\"/><input type=\"submit\" value=\"Load\" 
/></div></form>\n");
-                       //
-                       out.append("</div>\n");
-                       out.append("</div>\n");
-                       //
-                       ctx.getPageMaker().makeTail(out);
-                       writeReply(ctx, 200, "text/html", "OK", out.toString());
+                       HTMLNode addForm = infoboxContent.addChild("form", new 
String[] { "action", "method" }, new String[] { ".", "post" });
+                       addForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "formPassword", node.formPassword 
});
+                       HTMLNode loadDiv = addForm.addChild("div");
+                       loadDiv.addChild("#", "Load plugin: ");
+                       loadDiv.addChild("input", new String[] { "type", 
"name", "size" }, new String[] { "text", "load", "40" });
+                       loadDiv.addChild("input", new String[] { "type", 
"value" }, new String[] { "submit", "Load" });
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                } 
        }
 }

Modified: trunk/freenet/src/freenet/clients/http/QueueToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/QueueToadlet.java    2006-08-09 
19:01:12 UTC (rev 10005)
@@ -9,6 +9,7 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;

 import freenet.client.DefaultMIMETypes;
 import freenet.client.HighLevelSimpleClient;
@@ -25,6 +26,7 @@
 import freenet.node.fcp.MessageInvalidException;
 import freenet.support.HTMLDecoder;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.SizeUtil;
@@ -37,6 +39,19 @@

        private static final String[] priorityClasses = new String[] { 
"emergency", "very high", "high", "medium", "low", "very low", "will never 
finish" };

+       private static final int LIST_IDENTIFIER = 1;
+       private static final int LIST_SIZE = 2;
+       private static final int LIST_MIME_TYPE = 3;
+       private static final int LIST_DOWNLOAD = 4;
+       private static final int LIST_PERSISTENCE = 5;
+       private static final int LIST_KEY = 6;
+       private static final int LIST_FILENAME = 7;
+       private static final int LIST_PRIORITY = 8;
+       private static final int LIST_FILES = 9;
+       private static final int LIST_TOTAL_SIZE = 10;
+       private static final int LIST_PROGRESS = 11;
+       private static final int LIST_REASON = 12;
+       
        private Node node;
        final FCPServer fcp;

@@ -186,14 +201,15 @@
                this.handleGet(uri, ctx);
        }

-       private void writeError(String string, String string2, ToadletContext 
context) throws ToadletContextClosedException, IOException {
-               StringBuffer buffer = new StringBuffer();
-               context.getPageMaker().makeHead(buffer, "Error processing 
request");
-               writeBigHeading(string, buffer, null);
-               buffer.append(string2);
-               writeBigEnding(buffer);
-               context.getPageMaker().makeTail(buffer);
-               writeReply(context, 400, "text/html; charset=utf-8", "Error", 
buffer.toString());
+       private void writeError(String header, String message, ToadletContext 
context) throws ToadletContextClosedException, IOException {
+               PageMaker pageMaker = context.getPageMaker();
+               HTMLNode pageNode = pageMaker.getPageNode("Error process 
request");
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+               contentNode.addChild(node.alerts.createSummary());
+               HTMLNode infobox = 
contentNode.addChild(pageMaker.getInfobox("infobox-error", "Error process 
request"));
+               HTMLNode infoboxContent = pageMaker.getContentNode(infobox);
+               infoboxContent.addChild("#", message);
+               writeReply(context, 400, "text/html; charset=utf-8", "Error", 
pageNode.generate());
        }

        public void handleGet(URI uri, ToadletContext ctx) 
@@ -205,8 +221,7 @@
                        return;
                }

-               StringBuffer buf = new StringBuffer(2048);
-               
+               PageMaker pageMaker = ctx.getPageMaker();
                // First, get the queued requests, and separate them into 
different types.

                LinkedList completedDownloadToDisk = new LinkedList();
@@ -226,19 +241,13 @@
                Logger.minor(this, "Request count: "+reqs.length);

                if(reqs.length < 1){
-                       ctx.getPageMaker().makeHead(buf, "Global Queue of 
"+node.getMyName());
-                       buf.append("<div class=\"infobox 
infobox-information\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Global queue is empty!\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("There is no task queued on the global queue 
at the moment.\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       writeInsertBox(buf);
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = pageMaker.getPageNode("Global queue 
of " + node.getMyName());
+                       HTMLNode contentNode = 
pageMaker.getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(pageMaker.getInfobox("infobox-information", "Global queue 
is empty"));
+                       HTMLNode infoboxContent = 
pageMaker.getContentNode(infobox);
+                       infoboxContent.addChild("#", "There is no task queued 
on the global queue at the moment.");
+                       contentNode.addChild(createInsertBox(pageMaker));
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                }

@@ -299,550 +308,403 @@
                Collections.sort(uncompletedUpload, identifierComparator);
                Collections.sort(uncompletedDirUpload, identifierComparator);

-               ctx.getPageMaker().makeHead(buf, 
"("+(uncompletedDirUpload.size()+uncompletedDownload.size()+uncompletedUpload.size())+
-                               
"/"+(failedDirUpload.size()+failedDownload.size()+failedUpload.size())+
-                               
"/"+(completedDirUpload.size()+completedDownloadToDisk.size()+completedDownloadToTemp.size()+completedUpload.size())+
-                               ") Queued Requests");
+               HTMLNode pageNode = pageMaker.getPageNode("(" + 
(uncompletedDirUpload.size() + uncompletedDownload.size()
+                               + uncompletedUpload.size()) + "/" + 
(failedDirUpload.size() + failedDownload.size() + failedUpload.size()) + "/"
+                               + (completedDirUpload.size() + 
completedDownloadToDisk.size() + completedDownloadToTemp.size()
+                               + completedUpload.size()) + ") Queued Requests 
of " + node.getMyName());
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+
+               /* add alert summary box */
+               contentNode.addChild(node.alerts.createSummary());
+               /* add file insert box */
+               contentNode.addChild(createInsertBox(pageMaker));

-               node.alerts.toSummaryHtml(buf);
-               
-               writeInsertBox(buf);
-               
-               writeBigHeading("Legend", buf, "legend");
-               buf.append("<table class=\"queue\">\n");
-               buf.append("<tr>");
+               HTMLNode legendBox = 
contentNode.addChild(pageMaker.getInfobox("legend", "Legend"));
+               HTMLNode legendContent = pageMaker.getContentNode(legendBox);
+               HTMLNode legendTable = legendContent.addChild("table", "class", 
"queue");
+               HTMLNode legendRow = legendTable.addChild("tr");
                for(int i=0; i<7; i++){
-                       buf.append("<td class=\"priority"+i+"\">priority 
"+i+"</td>");
+                       legendRow.addChild("td", "class", "priority" + i, 
"Priority " + i);
                }
-               buf.append("</tr>\n");
-               writeTableEnd(buf);
-               if(reqs.length > 1)
-                       writeDeleteAll(buf);
-               writeBigEnding(buf);
+
+               if (reqs.length > 1) {
+                       contentNode.addChild(createPanicBox(pageMaker));
+               }
+
+               boolean advancedEnabled = 
node.getToadletContainer().isAdvancedDarknetEnabled();

-               if(!(completedDownloadToTemp.isEmpty() && 
completedDownloadToDisk.isEmpty() &&
-                               completedUpload.isEmpty() && 
completedDirUpload.isEmpty())) {
-                       writeBigHeading("Completed requests (" + 
(completedDownloadToTemp.size() + completedDownloadToDisk.size() + 
completedUpload.size() + completedDirUpload.size()) + ")", buf, 
"completed_requests");
-                       
-                       if(!completedDownloadToTemp.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Completed downloads to 
temporary space", new String[] { "", "Identifier", "Size", "MIME-Type", 
"Download", "Persistence", "Key" }, buf );
-                               else
-                                       writeTableHead("Completed downloads to 
temporary space", new String[] { "", "Size", "MIME-Type", "Download", 
"Persistence", "Key" }, buf );
-                               for(Iterator i = 
completedDownloadToTemp.iterator();i.hasNext();) {
-                                       ClientGet p = (ClientGet) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getURI(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeDownloadCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               if (!completedDownloadToTemp.isEmpty()) {
+                       HTMLNode completedDownloadsTempInfobox = 
contentNode.addChild(pageMaker.getInfobox("completed_requests", "Completed: 
Downloads to temporary directory (" + completedDownloadToTemp.size() + ")"));
+                       HTMLNode completedDownloadsToTempContent = 
pageMaker.getContentNode(completedDownloadsTempInfobox);
+                       if (advancedEnabled) {
+                               
completedDownloadsToTempContent.addChild(createRequestTable(pageMaker, 
completedDownloadToTemp, new int[] { LIST_IDENTIFIER, LIST_SIZE, 
LIST_MIME_TYPE, LIST_DOWNLOAD, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
completedDownloadsToTempContent.addChild(createRequestTable(pageMaker, 
completedDownloadToTemp, new int[] { LIST_SIZE, LIST_MIME_TYPE, LIST_DOWNLOAD, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-                       
-                       if(!completedDownloadToDisk.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Completed downloads to 
disk", new String[] { "", "Identifier", "Filename", "Size", "MIME-Type", 
"Download", "Persistence", "Key" }, buf);
-                               else
-                                       writeTableHead("Completed downloads to 
disk", new String[] { "", "Filename", "Size", "MIME-Type", "Download", 
"Persistence", "Key" }, buf);
-                               for(Iterator 
i=completedDownloadToDisk.iterator();i.hasNext();) {
-                                       ClientGet p = (ClientGet) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getURI(), buf);
-                                       writeFilenameCell(p.getDestFilename(), 
buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeDownloadCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               }
+               
+               if (!completedDownloadToDisk.isEmpty()) {
+                       HTMLNode completedToDiskInfobox = 
contentNode.addChild(pageMaker.getInfobox("completed_requests", "Completed: 
Downloads to download directory (" + completedDownloadToDisk.size() + ")"));
+                       HTMLNode completedToDiskInfoboxContent = 
pageMaker.getContentNode(completedToDiskInfobox);
+                       if (advancedEnabled) {
+                               
completedToDiskInfoboxContent.addChild(createRequestTable(pageMaker, 
completedDownloadToDisk, new int[] { LIST_IDENTIFIER, LIST_FILENAME, LIST_SIZE, 
LIST_MIME_TYPE, LIST_DOWNLOAD, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
completedToDiskInfoboxContent.addChild(createRequestTable(pageMaker, 
completedDownloadToDisk, new int[] { LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, 
LIST_DOWNLOAD, LIST_PERSISTENCE, LIST_KEY }));
                        }
+               }

-                       if(!completedUpload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Completed uploads", new 
String[] { "", "Identifier", "Filename", "Size", "MIME-Type", "Persistence", 
"Key" }, buf);
-                               else
-                                       writeTableHead("Completed uploads", new 
String[] { "", "Filename", "Size", "MIME-Type", "Persistence", "Key" }, buf);
-                               for(Iterator 
i=completedUpload.iterator();i.hasNext();) {
-                                       ClientPut p = (ClientPut) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                       if(p.isDirect())
-                                               writeDirectCell(buf);
-                                       else
-                                               
writeFilenameCell(p.getOrigFilename(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               if (!completedUpload.isEmpty()) {
+                       HTMLNode completedUploadInfobox = 
contentNode.addChild(pageMaker.getInfobox("completed_requests", "Completed: 
Uploads (" + completedUpload.size() + ")"));
+                       HTMLNode completedUploadInfoboxContent = 
pageMaker.getContentNode(completedUploadInfobox);
+                       if (advancedEnabled) {
+                               
completedUploadInfoboxContent.addChild(createRequestTable(pageMaker, 
completedUpload, new int[] { LIST_IDENTIFIER, LIST_FILENAME, LIST_SIZE, 
LIST_MIME_TYPE, LIST_PERSISTENCE, LIST_KEY }));
+                       } else  {
+                               
completedUploadInfoboxContent.addChild(createRequestTable(pageMaker, 
completedUpload, new int[] { LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-                       
-                       if(!completedDirUpload.isEmpty()) {
-                               // FIXME include filename??
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Completed directory 
uploads", new String[] { "", "Identifier", "Files", "Total Size", 
"Persistence", "Key" }, buf);
-                               else
-                                       writeTableHead("Completed directory 
uploads", new String[] { "", "Files", "Total Size", "Persistence", "Key" }, 
buf);
-                               for(Iterator 
i=completedDirUpload.iterator();i.hasNext();) {
-                                       ClientPutDir p = (ClientPutDir) 
i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                       writeNumberCell(p.getNumberOfFiles(), 
buf);
-                                       writeSizeCell(p.getTotalDataSize(), 
buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
-                       }
-                       writeBigEnding(buf);
                }

-               if(!(failedDownload.isEmpty() && failedUpload.isEmpty() && 
failedDirUpload.isEmpty())) {
-                       writeBigHeading("Failed requests (" + 
(failedDownload.size() + failedUpload.size() + failedDirUpload.size()) + ")", 
buf, "failed_requests");
-                       if(!failedDownload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Failed downloads", new 
String[] { "", "Identifier", "Filename", "Size", "MIME-Type", "Progress", 
"Reason", "Persistence", "Key" }, buf);
-                               else
-                                       writeTableHead("Failed downloads", new 
String[] { "", "Filename", "Size", "MIME-Type", "Progress", "Reason", 
"Persistence", "Key" }, buf);
-                               for(Iterator 
i=failedDownload.iterator();i.hasNext();) {
-                                       ClientGet p = (ClientGet) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getURI(), buf);
-                                       if(p.isDirect())
-                                               writeDirectCell(buf);
-                                       else
-                                               
writeFilenameCell(p.getDestFilename(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writeFailureReasonCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               if (!completedDirUpload.isEmpty()) {
+                       HTMLNode completedUploadDirInfobox = 
contentNode.addChild(pageMaker.getInfobox("completed_requests", "Completed: 
Directory Uploads (" + completedDirUpload.size() + ")"));
+                       HTMLNode completedUploadDirContent = 
pageMaker.getContentNode(completedUploadDirInfobox);
+                       if (advancedEnabled) {
+                               
completedUploadDirContent.addChild(createRequestTable(pageMaker, 
completedDirUpload, new int[] { LIST_IDENTIFIER, LIST_FILES, LIST_TOTAL_SIZE, 
LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
completedUploadDirContent.addChild(createRequestTable(pageMaker, 
completedDirUpload, new int[] { LIST_FILES, LIST_TOTAL_SIZE, LIST_PERSISTENCE, 
LIST_KEY }));
                        }
-                       
-                       if(!failedUpload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Failed uploads", new 
String[] { "", "Identifier", "Filename", "Size", "MIME-Type", "Progress", 
"Reason", "Persistence", "Key" }, buf);
-                               else
-                                       writeTableHead("Failed uploads", new 
String[] { "", "Filename", "Size", "MIME-Type", "Progress", "Reason", 
"Persistence", "Key" }, buf);
-                               for(Iterator 
i=failedUpload.iterator();i.hasNext();) {
-                                       ClientPut p = (ClientPut) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                       if(p.isDirect())
-                                               writeDirectCell(buf);
-                                       else
-                                               
writeFilenameCell(p.getOrigFilename(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writeFailureReasonCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               }
+                               
+               if (!failedDownload.isEmpty()) {
+                       HTMLNode failedInfobox = 
contentNode.addChild(pageMaker.getInfobox("failed_requests", "Failed: Downloads 
(" + failedDownload.size() + ")"));
+                       HTMLNode failedContent = 
pageMaker.getContentNode(failedInfobox);
+                       if (advancedEnabled) {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedDownload, new int[] 
{ LIST_IDENTIFIER, LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, 
LIST_REASON, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedDownload, new int[] 
{ LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, LIST_REASON, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-                       
-                       if(!failedDirUpload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Failed directory 
uploads", new String[] { "", "Identifier", "Files", "Total Size", "Progress", 
"Reason", "Persistence", "Key" }, buf);
-                               else
-                                       writeTableHead("Failed directory 
uploads", new String[] { "", "Files", "Total Size", "Progress", "Reason", 
"Persistence", "Key" }, buf);
-                               for(Iterator 
i=failedDirUpload.iterator();i.hasNext();) {
-                                       ClientPutDir p = (ClientPutDir) 
i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                       writeNumberCell(p.getNumberOfFiles(), 
buf);
-                                       writeSizeCell(p.getTotalDataSize(), 
buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writeFailureReasonCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               }
+               
+               if (!failedUpload.isEmpty()) {
+                       HTMLNode failedInfobox = 
contentNode.addChild(pageMaker.getInfobox("failed_requests", "Failed: Uploads 
(" + failedUpload.size() + ")"));
+                       HTMLNode failedContent = 
pageMaker.getContentNode(failedInfobox);
+                       if (advancedEnabled) {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedUpload, new int[] { 
LIST_IDENTIFIER, LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, 
LIST_REASON, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedUpload, new int[] { 
LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, LIST_REASON, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-                       writeBigEnding(buf);
                }

-               if(!(uncompletedDownload.isEmpty() && 
uncompletedUpload.isEmpty() && 
-                               uncompletedDirUpload.isEmpty())) {
-                       writeBigHeading("Requests in progress (" + 
(uncompletedDownload.size() + uncompletedUpload.size() + 
uncompletedDirUpload.size()) + ")", buf, "requests_in_progress");
-                       if(!uncompletedDownload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Downloads in progress", 
new String[] {
-                                                       "", "Identifier", 
"Filename", "Priority", "Size", "MIME-Type", "Progress", "Persistence", "Key" 
-                                       }, buf);
-                               else
-                                       writeTableHead("Downloads in progress", 
new String[] {
-                                                       "", "Filename", "Size", 
"MIME-Type", "Progress", "Persistence", "Key" 
-                                       }, buf);
-                               for(Iterator i = 
uncompletedDownload.iterator();i.hasNext();) {
-                                       ClientGet p = (ClientGet) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getURI(), buf);
-                                       if(p.isDirect())
-                                               writeDirectCell(buf);
-                                       else
-                                               
writeFilenameCell(p.getDestFilename(), buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled()) 
-                                               
writePriorityCell(p.getIdentifier(), p.getPriority(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               if (!failedDirUpload.isEmpty()) {
+                       HTMLNode failedInfobox = 
contentNode.addChild(pageMaker.getInfobox("failed_requests", "Failed: Directory 
Uploads (" + failedDirUpload.size() + ")"));
+                       HTMLNode failedContent = 
pageMaker.getContentNode(failedInfobox);
+                       if (advancedEnabled) {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedDirUpload, new int[] 
{ LIST_IDENTIFIER, LIST_FILES, LIST_TOTAL_SIZE, LIST_PROGRESS, LIST_REASON, 
LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
failedContent.addChild(createRequestTable(pageMaker, failedDirUpload, new int[] 
{ LIST_FILES, LIST_TOTAL_SIZE, LIST_PROGRESS, LIST_REASON, LIST_PERSISTENCE, 
LIST_KEY }));
                        }
-
-                       if(!uncompletedUpload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Uploads in progress", 
new String[] { 
-                                                       "", "Identifier", 
"Filename", "Priority",  "Size", "MIME-Type", "Progress", "Persistence", "Key" 
-                                       }, buf);
-                               else
-                                       writeTableHead("Uploads in progress", 
new String[] {
-                                                       "", "Filename", "Size", 
"MIME-Type", "Progress", "Persistence", "Key" 
-                                       }, buf);
-                               for(Iterator i = 
uncompletedUpload.iterator();i.hasNext();) {
-                                       ClientPut p = (ClientPut) i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                       if(p.isDirect())
-                                               writeDirectCell(buf);
-                                       else
-                                               
writeFilenameCell(p.getOrigFilename(), buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                               
writePriorityCell(p.getIdentifier(), p.getPriority(), buf);
-                                       writeSizeCell(p.getDataSize(), buf);
-                                       writeTypeCell(p.getMIMEType(), buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               }
+               
+               if (!uncompletedDownload.isEmpty()) {
+                       HTMLNode uncompletedInfobox = 
contentNode.addChild(pageMaker.getInfobox("requests_in_progress", "In Progress: 
Downloads (" + uncompletedDownload.size() + ")"));
+                       HTMLNode uncompletedContent = 
pageMaker.getContentNode(uncompletedInfobox);
+                       if (advancedEnabled) {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedDownload, 
new int[] { LIST_IDENTIFIER, LIST_FILENAME, LIST_PRIORITY, LIST_SIZE, 
LIST_MIME_TYPE, LIST_PROGRESS, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedDownload, 
new int[] { LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-
-                       if(!uncompletedDirUpload.isEmpty()) {
-                               if 
(node.getToadletContainer().isAdvancedDarknetEnabled())
-                                       writeTableHead("Directory uploads in 
progress", new String[] {
-                                                       "", "Identifier", 
"Priority", "Files", "Total Size", "Progress", "Persistence", "Key"
-                                       }, buf);
-                               else
-                                       writeTableHead("Directory uploads in 
progress", new String[] {
-                                                       "", "Files", "Total 
Size", "Progress", "Persistence", "Key"
-                                       }, buf);
-                               for(Iterator 
i=uncompletedDirUpload.iterator();i.hasNext();) {
-                                       ClientPutDir p = (ClientPutDir) 
i.next();
-                                       writeRowStart(buf,p);
-                                       writeDeleteCell(p, buf);
-                                       if 
(node.getToadletContainer().isAdvancedDarknetEnabled()){
-                                               writeIdentifierCell(p, 
p.getFinalURI(), buf);
-                                               
writePriorityCell(p.getIdentifier(), p.getPriority(), buf);
-                                       }
-                                       writeNumberCell(p.getNumberOfFiles(), 
buf);
-                                       writeSizeCell(p.getTotalDataSize(), 
buf);
-                                       writeProgressFractionCell(p, buf);
-                                       writePersistenceCell(p, buf);
-                                       writeKeyCell(p.getFinalURI(), buf);
-                                       writeRowEnd(buf);
-                               }
-                               writeTableEnd(buf);
+               }
+               
+               if (!uncompletedUpload.isEmpty()) {
+                       HTMLNode uncompletedInfobox = 
contentNode.addChild(pageMaker.getInfobox("requests_in_progress", "In Progress: 
Uploads (" + uncompletedUpload.size() + ")"));
+                       HTMLNode uncompletedContent = 
pageMaker.getContentNode(uncompletedInfobox);
+                       if (advancedEnabled) {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedUpload, 
new int[] { LIST_IDENTIFIER, LIST_FILENAME, LIST_PRIORITY, LIST_SIZE, 
LIST_MIME_TYPE, LIST_PROGRESS, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedUpload, 
new int[] { LIST_FILENAME, LIST_SIZE, LIST_MIME_TYPE, LIST_PROGRESS, 
LIST_PERSISTENCE, LIST_KEY }));
                        }
-                       writeBigEnding(buf);
                }

-               ctx.getPageMaker().makeTail(buf);
-
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+               if (!uncompletedDirUpload.isEmpty()) {
+                       HTMLNode uncompletedInfobox = 
contentNode.addChild(pageMaker.getInfobox("requests_in_progress", "In Progress: 
Downloads (" + uncompletedDownload.size() + ")"));
+                       HTMLNode uncompletedContent = 
pageMaker.getContentNode(uncompletedInfobox);
+                       if (advancedEnabled) {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedDirUpload, 
new int[] { LIST_IDENTIFIER, LIST_FILES, LIST_PRIORITY, LIST_TOTAL_SIZE, 
LIST_PROGRESS, LIST_PERSISTENCE, LIST_KEY }));
+                       } else {
+                               
uncompletedContent.addChild(createRequestTable(pageMaker, uncompletedDirUpload, 
new int[] { LIST_FILES, LIST_TOTAL_SIZE, LIST_PROGRESS, LIST_PERSISTENCE, 
LIST_KEY }));
+                       }
+               }
+               
+               this.writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
        }


-       private void writeFailureReasonCell(ClientRequest p, StringBuffer buf) {
-               buf.append("<td>");
-               String s = p.getFailureReason();
-               if(s == null)
-                       buf.append("<span 
class=\"failure_reason_unknown\">unknown</span>");
-               else
-                       buf.append("<span class=\"failure_reason_is\">" + s + 
"</span>");
-               buf.append("</td>\n");
+       private HTMLNode createReasonCell(String failureReason) {
+               HTMLNode reasonCell = new HTMLNode("td", "class", 
"request-reason");
+               if (failureReason == null) {
+                       reasonCell.addChild("span", "class", 
"failure_reason_unknown", "unknown");
+               } else {
+                       reasonCell.addChild("span", "class", 
"failure_reason_is", failureReason);
+               }
+               return reasonCell;
        }

-       private void writeProgressFractionCell(ClientRequest p, StringBuffer 
buf) {
-               buf.append("<td>");
-               
-               if(!p.isStarted()) {
-                       buf.append("STARTING</td>");
-                       return;
+       private HTMLNode createProgressCell(boolean started, int fetched, int 
failed, int fatallyFailed, int min, int total, boolean finalized) {
+               HTMLNode progressCell = new HTMLNode("td", "class", 
"request-progress");
+               if (!started) {
+                       progressCell.addChild("#", "STARTING");
+                       return progressCell;
                }

                //double frac = p.getSuccessFraction();
-               double total;
-               if(node.getToadletContainer().isAdvancedDarknetEnabled())
-                       total = p.getTotalBlocks();
-               else
-                       total = p.getMinBlocks();
-               // All are fractions
-               double fetched = p.getFetchedBlocks()/total;
-               double failed = p.getFailedBlocks()/total;
-               double failed2 = p.getFatalyFailedBlocks()/total;
-               double min = p.getMinBlocks()/total;
-
-               if (Double.isNaN(fetched)) fetched = 0.0;
-               if (Double.isNaN(failed)) failed = 0.0;
-               if (Double.isNaN(failed2)) failed2 = 0.0;
-               if (Double.isNaN(min)) min = 0.0;
-               if(min == 0.0) min = 1.0;
+               if (!node.getToadletContainer().isAdvancedDarknetEnabled()) {
+                       total = min;
+               }

-               boolean b = p.isTotalFinalized();
-               if(fetched < 0) {
-                       buf.append("<span 
class=\"progress_fraction_unknown\">unknown</span>");
+               if ((fetched < 0) || (total <= 0)) {
+                       progressCell.addChild("span", "class", 
"progress_fraction_unknown", "unknown");
                } else {
-                       NumberFormat nf = NumberFormat.getInstance();
-                       nf.setMaximumFractionDigits(0);
-                       buf.append("<div class=\"progressbar\">"+
-                                       "<div class=\"progressbar-done\" 
style=\"width: "+nf.format(fetched*100)+"px\"></div>");
-                       
if(node.getToadletContainer().isAdvancedDarknetEnabled())
-                       {
-                               if(failed > 0)
-                                       buf.append("<div 
class=\"progressbar-failed\" style=\"width: 
"+nf.format(failed*100)+"px\"></div>");
-                               if(failed2 > 0)
-                                       buf.append("<div 
class=\"progressbar-failed2\" style=\"width: 
"+nf.format(failed2*100)+"px\"></div>");
-                               if(fetched < min)
-                                       buf.append("<div 
class=\"progressbar-min\" style=\"width: 
"+nf.format((min-fetched)*100)+"px\"></div>");
-                       }
-                       buf.append("</div>");
+                       int fetchedPercent = (int) (fetched / (double) total * 
100);
+                       int failedPercent = (int) (failed / (double) total * 
100);
+                       int fatallyFailedPercent = (int) (fatallyFailed / 
(double) total * 100);
+                       int minPercent = (int) (min / (double) total * 100);
+                       HTMLNode progressBar = progressCell.addChild("div", 
"class", "progressbar");
+                       progressBar.addChild("div", new String[] { "class", 
"style" }, new String[] { "progressbar-done", "width: " + fetchedPercent + "%;" 
});
+
+                       if (failed > 0)
+                               progressBar.addChild("div", new String[] { 
"class", "style" }, new String[] { "progressbar-failed", "width: " + 
failedPercent + "%;" });
+                       if (fatallyFailed > 0)
+                               progressBar.addChild("div", new String[] { 
"class", "style" }, new String[] { "progressbar-failed2", "width: " + 
fatallyFailedPercent + "%;" });
+                       if ((fetched + failed + fatallyFailed) < min)
+                               progressBar.addChild("div", new String[] { 
"class", "style" }, new String[] { "progressbar-min", "width: " + (minPercent - 
fetchedPercent) + "%;" });

+                       NumberFormat nf = NumberFormat.getInstance();
                        nf.setMaximumFractionDigits(1);
-                       if(b)
-                               buf.append("<span 
class=\"progress_fraction_finalized\">");
-                       else
-                               buf.append("<span 
class=\"progress_fraction_not_finalized\">");
-                       buf.append(nf.format(fetched*100));
-                       buf.append("%</span>");
+                       if (finalized) {
+                               progressBar.addChild("div", new String[] { 
"class", "title" }, new String[] { "progress_fraction_finalized", "finalized" 
}, (int) ((fetched / (double) min) * 100) + "%");
+                       } else {
+                               progressBar.addChild("div", new String[] { 
"class", "title" }, new String[] { "progress_fraction_not_finalized", "not 
finalized" }, (int) ((fetched / (double) min) * 100) + "%");
+                       }
                }
-               buf.append("</td>\n");
+               return progressCell;
        }

-       private void writeNumberCell(int numberOfFiles, StringBuffer buf) {
-               buf.append("<td>");
-               buf.append("<span class=\"number_of_files\">" + numberOfFiles + 
"</span>");
-               buf.append("</td>\n");
+       private HTMLNode createNumberCell(int numberOfFiles) {
+               HTMLNode numberCell = new HTMLNode("td", "class", 
"request-files");
+               numberCell.addChild("span", "class", "number_of_files", 
String.valueOf(numberOfFiles));
+               return numberCell;
        }

-       private void writeDirectCell(StringBuffer buf) {
-               buf.append("<td>");
-               buf.append("<span class=\"filename_direct\">direct</span>");
-               buf.append("</td>\n");
+       private HTMLNode createFilenameCell(File filename) {
+               HTMLNode filenameCell = new HTMLNode("td", "class", 
"request-filename");
+               if (filename != null) {
+                       filenameCell.addChild("span", "class", "filename_is", 
filename.toString());
+               } else {
+                       filenameCell.addChild("span", "class", "filename_none", 
"none");
+               }
+               return filenameCell;
        }

-       private void writeFilenameCell(File destFilename, StringBuffer buf) {
-               buf.append("<td>");
-               if(destFilename == null)
-                       buf.append("<span class=\"filename_none\">none</span>");
-               else
-                       buf.append("<span class=\"filename_is\">" + 
HTMLEncoder.encode(destFilename.toString()) + "</span>");
-               buf.append("</td>\n");
-       }
-
-       private void writePriorityCell(String identifier, short priorityClass, 
StringBuffer buf) {
-               buf.append("<td class=\"nowrap\">");
-               buf.append("<form action=\"/queue/\" method=\"post\">");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\"").append(node.formPassword).append("\" />");
-               buf.append("<input type=\"hidden\" name=\"identifier\" 
value=\"").append(HTMLEncoder.encode(identifier)).append("\" />");
-               buf.append("<select name=\"priority\">");
+       private HTMLNode createPriorityCell(PageMaker pageMaker, String 
identifier, short priorityClass) {
+               HTMLNode priorityCell = new HTMLNode("td", "class", 
"request-priority nowrap");
+               HTMLNode priorityForm = priorityCell.addChild("form", new 
String[] { "action", "method" }, new String[] { "/queue/", "post" });
+               
priorityForm.addChild(pageMaker.createFormPasswordInput(node.formPassword));
+               priorityForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "identifier", identifier });
+               HTMLNode prioritySelect = priorityForm.addChild("select", 
"name", "priority");
                for (int p = 0; p < RequestStarter.NUMBER_OF_PRIORITY_CLASSES; 
p++) {
-                       buf.append("<option value=\"").append(p);
                        if (p == priorityClass) {
-                               buf.append("\" selected=\"selected");
+                               prioritySelect.addChild("option", new String[] 
{ "value", "selected" }, new String[] { String.valueOf(p), "selected" }, 
priorityClasses[p]);
+                       } else {
+                               prioritySelect.addChild("option", "value", 
String.valueOf(p), priorityClasses[p]);
                        }
-                       buf.append("\">");
-                       buf.append(priorityClasses[p]);
-                       buf.append("</option>");
                }
-               buf.append("</select>");
-               buf.append("<input type=\"submit\" name=\"change_priority\" 
value=\"Change\" />");
-               buf.append("</form>");
-               buf.append("</td>");
+               priorityForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "submit", "change_priority", "Change" });
+               return priorityCell;
        }

-       private void writeDeleteCell(ClientRequest p, StringBuffer buf) {
-               buf.append("<td>");
-               buf.append("<form action=\"/queue/\" method=\"post\">");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\""+node.formPassword+"\" />");
-               buf.append("<input type=\"hidden\" name=\"identifier\" 
value=\"");
-               buf.append(HTMLEncoder.encode(p.getIdentifier()));
-               buf.append("\" /><input type=\"submit\" name=\"remove_request\" 
value=\"Delete\" />");
-               buf.append("</form>\n");
-               buf.append("</td>\n");
+       private HTMLNode createDeleteCell(PageMaker pageMaker, String 
identifier) {
+               HTMLNode deleteNode = new HTMLNode("td", "class", 
"request-delete");
+               HTMLNode deleteForm = deleteNode.addChild("form", new String[] 
{ "action", "method" }, new String[] { "/queue/", "post" });
+               
deleteForm.addChild(pageMaker.createFormPasswordInput(node.formPassword));
+               deleteForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "identifier", identifier });
+               deleteForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "submit", "remove_request", "Delete" });
+               return deleteNode;
        }

-       private void writeDeleteAll(StringBuffer buf) {
-               buf.append("<td>");
-               buf.append("<form action=\"/queue/\" method=\"post\">");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\""+node.formPassword+"\" />");
-               buf.append("<input type=\"submit\" name=\"remove_AllRequests\" 
value=\"Delete Everything\" />");
-               buf.append("</form>\n");
-               buf.append("</td>\n");
+       private HTMLNode createPanicBox(PageMaker pageMaker) {
+               HTMLNode panicBox = pageMaker.getInfobox("infobox-alert", 
"Panic Button");
+               HTMLNode panicForm = 
pageMaker.getContentNode(panicBox).addChild("form", new String[] { "action", 
"method" }, new String[] { "/queue/", "post" });
+               
panicForm.addChild(pageMaker.createFormPasswordInput(node.formPassword));
+               panicForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "submit", "remove_AllRequests", "Delete everything 
without confirmation!" });
+               return panicBox;
        }

-       private void writeIdentifierCell(ClientRequest p, FreenetURI uri, 
StringBuffer buf) {
-               buf.append("<td>");
-               if(uri != null) {
-                       buf.append("<span class=\"identifier_with_uri\"><a 
href=\"/");
-                       buf.append(HTMLEncoder.encode(uri.toString(false)));
-                       buf.append("\">");
-                       buf.append(HTMLEncoder.encode(p.getIdentifier()));
-                       buf.append("</a></span>");
+       private HTMLNode createIdentifierCell(FreenetURI uri, String 
identifier) {
+               HTMLNode identifierCell = new HTMLNode("td", "class", 
"request-identifier");
+               if (uri != null) {
+                       identifierCell.addChild("span", "class", 
"identifier_with_uri").addChild("a", "href", "/" + uri, identifier);
+               } else {
+                       identifierCell.addChild("span", "class", 
"identifier_without_uri", identifier);
                }
-               else {
-                       buf.append("<span class=\"identifier_without_uri\">");
-                       buf.append(HTMLEncoder.encode(p.getIdentifier()));
-                       buf.append("</span>");
-               }
-               buf.append("</td>\n");
+               return identifierCell;
        }

-       private void writePersistenceCell(ClientRequest p, StringBuffer buf) {
-               buf.append("<td>");
-               if(!p.isPersistent())
-                       buf.append("<span 
class=\"persistence_none\">none</span>");
-               else if(!p.isPersistentForever())
-                       buf.append("<span 
class=\"persistence_reboot\">reboot</span>");
-               else
-                       buf.append("<span 
class=\"persistence_forever\">forever</span>");
-               buf.append("</td>\n");
+       private HTMLNode createPersistenceCell(boolean persistent, boolean 
persistentForever) {
+               HTMLNode persistenceCell = new HTMLNode("td", "class", 
"request-persistence");
+               if (persistentForever) {
+                       persistenceCell.addChild("span", "class", 
"persistence_forever", "forever");
+               } else if (persistent) {
+                       persistenceCell.addChild("span", "class", 
"persistence_reboot", "reboot");
+               } else {
+                       persistenceCell.addChild("span", "class", 
"persistence_none", "none");
+               }
+               return persistenceCell;
        }

-       private void writeDownloadCell(ClientGet p, StringBuffer buf) {
-               buf.append("<td>");
-               buf.append("FIXME");
-               buf.append("</td>");
+       private HTMLNode createDownloadCell(ClientGet p) {
+               return new HTMLNode("td", "class", "request-download", 
"FIXME"); /* TODO */
        }

-       private void writeTypeCell(String type, StringBuffer buf) {
-               buf.append("<td>");
-               if(type != null)
-                       buf.append("<span class=\"mimetype_is\">" + type + 
"</span>");
-               else
-                       buf.append("<span 
class=\"mimetype_unknown\">unknown</span>");
-               buf.append("</td>\n");
+       private HTMLNode createTypeCell(String type) {
+               HTMLNode typeCell = new HTMLNode("td", "class", "request-type");
+               if (type != null) {
+                       typeCell.addChild("span", "class", "mimetype_is", type);
+               } else {
+                       typeCell.addChild("span", "class", "mimetype_unknown", 
"unknown");
+               }
+               return typeCell;
        }

-       private void writeSizeCell(long dataSize, StringBuffer buf) {
-               buf.append("<td>");
-               if(dataSize >= 0)
-                       buf.append("<span class=\"filesize_is\">" + 
SizeUtil.formatSize(dataSize) + "</span>");
-               else
-                       buf.append("<span 
class=\"filesize_unknown\">unknown</span>");
-               buf.append("</td>\n");
-       }
-
-       private void writeKeyCell(FreenetURI uri, StringBuffer buf) {
-               buf.append("<td>");
-               if(uri != null) {
-                       buf.append("<span class=\"key_is\"><a href=\"/");
-                       String u = uri.toString(false);
-                       buf.append(URLEncoder.encode(u));
-                       buf.append("\">");
-                       u = uri.toShortString();
-                       buf.append(HTMLEncoder.encode(u));
-                       buf.append("</a></span>");
+       private HTMLNode createSizeCell(long dataSize) {
+               HTMLNode sizeCell = new HTMLNode("td", "class", "request-size");
+               if (dataSize >= 0) {
+                       sizeCell.addChild("span", "class", "filesize_is", 
SizeUtil.formatSize(dataSize));
                } else {
-                       buf.append("<span 
class=\"key_unknown\">unknown</span>");
+                       sizeCell.addChild("span", "class", "filesize_unknown", 
"unknown");
                }
-               buf.append("</td>\n");
+               return sizeCell;
        }

-       private void writeRowStart(StringBuffer buf, ClientRequest p) {
-               buf.append("<tr class=\"priority"+p.getPriority()+"\">");
-       }
-
-       private void writeRowEnd(StringBuffer buf) {
-               buf.append("</tr>\n");
-       }
-
-       private void writeTableHead(String tabletitle, String[] strings, 
StringBuffer buf) {
-               buf.append("<div class=\"queue_tabletitle\">" + tabletitle + 
"</div>");
-               buf.append("<table class=\"queue\">\n");
-               buf.append("<tr>");
-               for(int i=0;i<strings.length;i++) {
-                       buf.append("<th>");
-                       buf.append(strings[i]);
-                       buf.append("</th>");
+       private HTMLNode createKeyCell(FreenetURI uri) {
+               HTMLNode keyCell = new HTMLNode("td", "class", "request-key");
+               if (uri != null) {
+                       keyCell.addChild("span", "class", 
"key_is").addChild("a", "href", "/" + uri.toString(false), uri.toShortString());
+               } else {
+                       keyCell.addChild("span", "class", "key_unknown", 
"unknown");
                }
-               buf.append("</tr>\n");
+               return keyCell;
        }
-
-       private void writeTableEnd(StringBuffer buf) {
-               buf.append("</table>");
-       }
-
-       private void writeBigHeading(String header, StringBuffer buf, String 
id) {
-               buf.append("<div class=\"infobox infobox-normal\"" + ((id != 
null) ? " id=\"" + id + "\"" : "") + ">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append(header);
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-       }
-
-       private void writeBigEnding(StringBuffer buf) {
-               buf.append("</div>\n");
-               buf.append("</div>\n");
-       }

-       private void writeInsertBox(StringBuffer buf) {
+       private HTMLNode createInsertBox(PageMaker pageMaker) {
                /* the insert file box */
-               buf.append("<div class=\"infobox\">");
-               buf.append("<div class=\"infobox-header\">Insert File</div>");
-               buf.append("<div class=\"infobox-content\">");
-               buf.append("<form action=\".\" method=\"post\" 
enctype=\"multipart/form-data\">");
-               buf.append("<input type=\"hidden\" name=\"formPassword\" 
value=\"").append(node.formPassword).append("\" />");
-               buf.append("Insert as: <input type=\"radio\" name=\"keytype\" 
value=\"chk\" checked /> CHK &nbsp; ");
-               buf.append("<input type=\"radio\" name=\"keytype\" 
value=\"ksk\" /> KSK &nbsp; ");
-               buf.append("<input type=\"text\" name=\"key\" value=\"KSK@\" /> 
&nbsp; ");
-               buf.append("File: <input type=\"file\" name=\"filename\" 
value=\"\" /> &nbsp; ");
-               buf.append("<input type=\"checkbox\" name=\"dontCompress\" /> 
Don&rsquo;t Compress &nbsp; ");
-               buf.append("<input type=\"submit\" name=\"insert\" 
value=\"Insert File\" /> &nbsp; ");
-               buf.append("<input type=\"reset\" value=\"Reset Form\" />");
-               buf.append("</form>");
-               buf.append("</div>");
-               buf.append("</div>\n");
+               HTMLNode insertBox = pageMaker.getInfobox("Insert File");
+               HTMLNode insertForm = 
pageMaker.getContentNode(insertBox).addChild("form", new String[] { "action", 
"method", "enctype" }, new String[] { ".", "post", "multipart/form-data" });
+               
insertForm.addChild(pageMaker.createFormPasswordInput(node.formPassword));
+               insertForm.addChild("#", "Insert as: ");
+               insertForm.addChild("input", new String[] { "type", "name", 
"value", "checked" }, new String[] { "radio", "keytype", "chk", "checked" });
+               insertForm.addChild("#", " CHK \u00a0 ");
+               insertForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "radio", "keytype", "ksk" });
+               insertForm.addChild("#", " KSK \u00a0 ");
+               insertForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "text", "key", "KSK@" });
+               insertForm.addChild("#", " \u00a0 File: ");
+               insertForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "file", "filename", "" });
+               insertForm.addChild("#", " \u00a0 ");
+               insertForm.addChild("input", new String[] { "type", "name" }, 
new String[] { "checkbox", "dontCompress" });
+               insertForm.addChild("#", " Don\u2019t compress \u00a0 ");
+               insertForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "submit", "insert", "Insert file" });
+               insertForm.addChild("#", " \u00a0 ");
+               insertForm.addChild("input", new String[] { "type", "name" }, 
new String[] { "reset", "Reset form" });
+               return insertBox;
        }
+       
+       private HTMLNode createRequestTable(PageMaker pageMaker, List requests, 
int[] columns) {
+               HTMLNode table = new HTMLNode("table");
+               HTMLNode headerRow = table.addChild("tr", "class", 
"table-header");
+               headerRow.addChild("th");
+               for (int columnIndex = 0, columnCount = columns.length; 
columnIndex < columnCount; columnIndex++) {
+                       int column = columns[columnIndex];
+                       if (column == LIST_IDENTIFIER) {
+                               headerRow.addChild("th", "Identifier");
+                       } else if (column == LIST_SIZE) {
+                               headerRow.addChild("th", "Size");
+                       } else if (column == LIST_DOWNLOAD) {
+                               headerRow.addChild("th", "Download");
+                       } else if (column == LIST_MIME_TYPE) {
+                               headerRow.addChild("th", "MIME Type");
+                       } else if (column == LIST_PERSISTENCE) {
+                               headerRow.addChild("th", "Persistence");
+                       } else if (column == LIST_KEY) {
+                               headerRow.addChild("th", "Key");
+                       } else if (column == LIST_FILENAME) {
+                               headerRow.addChild("th", "Filename");
+                       } else if (column == LIST_PRIORITY) {
+                               headerRow.addChild("th", "Priority");
+                       } else if (column == LIST_FILES) {
+                               headerRow.addChild("th", "Files");
+                       } else if (column == LIST_TOTAL_SIZE) {
+                               headerRow.addChild("th", "Total Size");
+                       } else if (column == LIST_PROGRESS) {
+                               headerRow.addChild("th", "Progress");
+                       } else if (column == LIST_REASON) {
+                               headerRow.addChild("th", "Reason");
+                       }
+               }
+               for (Iterator requestItems = requests.iterator(); 
requestItems.hasNext(); ) {
+                       ClientRequest clientRequest = (ClientRequest) 
requestItems.next();
+                       HTMLNode requestRow = table.addChild("tr", "class", 
"priority" + clientRequest.getPriority());
+                       
+                       requestRow.addChild(createDeleteCell(pageMaker, 
clientRequest.getIdentifier()));
+                       for (int columnIndex = 0, columnCount = columns.length; 
columnIndex < columnCount; columnIndex++) {
+                               int column = columns[columnIndex];
+                               if (column == LIST_IDENTIFIER) {
+                                       if (clientRequest instanceof ClientGet) 
{
+                                               
requestRow.addChild(createIdentifierCell(((ClientGet) clientRequest).getURI(), 
clientRequest.getIdentifier()));
+                                       } else if (clientRequest instanceof 
ClientPutDir) {
+                                               
requestRow.addChild(createIdentifierCell(((ClientPutDir) 
clientRequest).getFinalURI(), clientRequest.getIdentifier()));
+                                       } else if (clientRequest instanceof 
ClientPut) {
+                                               
requestRow.addChild(createIdentifierCell(((ClientPut) 
clientRequest).getFinalURI(), clientRequest.getIdentifier()));
+                                       }
+                               } else if (column == LIST_SIZE) {
+                                       if (clientRequest instanceof ClientGet) 
{
+                                               
requestRow.addChild(createSizeCell(((ClientGet) clientRequest).getDataSize()));
+                                       } else if (clientRequest instanceof 
ClientPut) {
+                                               
requestRow.addChild(createSizeCell(((ClientPut) clientRequest).getDataSize()));
+                                       }
+                               } else if (column == LIST_DOWNLOAD) {
+                                       
requestRow.addChild(createDownloadCell((ClientGet) clientRequest));
+                               } else if (column == LIST_MIME_TYPE) {
+                                       if (clientRequest instanceof ClientGet) 
{
+                                               
requestRow.addChild(createTypeCell(((ClientGet) clientRequest).getMIMEType()));
+                                       } else if (clientRequest instanceof 
ClientPut) {
+                                               
requestRow.addChild(createTypeCell(((ClientPut) clientRequest).getMIMEType()));
+                                       }
+                               } else if (column == LIST_PERSISTENCE) {
+                                       
requestRow.addChild(createPersistenceCell(clientRequest.isPersistent(), 
clientRequest.isPersistentForever()));
+                               } else if (column == LIST_KEY) {
+                                       if (clientRequest instanceof ClientGet) 
{
+                                               
requestRow.addChild(createKeyCell(((ClientGet) clientRequest).getURI()));
+                                       } else {
+                                               
requestRow.addChild(createKeyCell(((ClientPut) clientRequest).getFinalURI()));
+                                       }
+                               } else if (column == LIST_FILENAME) {
+                                       if (clientRequest instanceof ClientGet) 
{
+                                               
requestRow.addChild(createFilenameCell(((ClientGet) 
clientRequest).getDestFilename()));
+                                       } else if (clientRequest instanceof 
ClientPut) {
+                                               
requestRow.addChild(createFilenameCell(((ClientPut) 
clientRequest).getOrigFilename()));
+                                       }
+                               } else if (column == LIST_PRIORITY) {
+                                       
requestRow.addChild(createPriorityCell(pageMaker, 
clientRequest.getIdentifier(), clientRequest.getPriority()));
+                               } else if (column == LIST_FILES) {
+                                       
requestRow.addChild(createNumberCell(((ClientPutDir) 
clientRequest).getNumberOfFiles()));
+                               } else if (column == LIST_TOTAL_SIZE) {
+                                       
requestRow.addChild(createSizeCell(((ClientPutDir) 
clientRequest).getTotalDataSize()));
+                               } else if (column == LIST_PROGRESS) {
+                                       
requestRow.addChild(createProgressCell(clientRequest.isStarted(), (int) 
clientRequest.getFetchedBlocks(), (int) clientRequest.getFailedBlocks(), (int) 
clientRequest.getFatalyFailedBlocks(), (int) clientRequest.getMinBlocks(), 
(int) clientRequest.getTotalBlocks(), clientRequest.isTotalFinalized()));
+                               } else if (column == LIST_REASON) {
+                                       
requestRow.addChild(createReasonCell(clientRequest.getFailureReason()));
+                               }
+                       }
+               }
+               return table;
+       }

        public String supportedMethods() {
                return "GET, POST";

Modified: trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java     
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java     
2006-08-09 19:01:12 UTC (rev 10005)
@@ -53,6 +53,7 @@
        private String cssName;
        private Thread myThread;
        private boolean advancedDarknetEnabled;
+       private final PageMaker pageMaker;

        static final int DEFAULT_FPROXY_PORT = 8888;

@@ -228,6 +229,7 @@
                if((cssName.indexOf(':') != -1) || (cssName.indexOf('/') != -1))
                        throw new InvalidConfigValueException("CSS name must 
not contain slashes or colons!");
                this.advancedDarknetEnabled = 
fproxyConfig.getBoolean("advancedDarknetEnabled");
+               pageMaker = new PageMaker(cssName);

                toadlets = new LinkedList();
                node.setToadletContainer(this); // even if not enabled, because 
of config
@@ -250,6 +252,7 @@
                this.networkInterface = new NetworkInterface(port, this.bindTo, 
this.allowedHosts);
                toadlets = new LinkedList();
                this.cssName = cssName;
+               pageMaker = new PageMaker(cssName);
        }

        public void start() {
@@ -261,10 +264,17 @@
        }

        public void register(Toadlet t, String urlPrefix, boolean atFront) {
+               register(t, urlPrefix, atFront, null, null);
+       }
+       
+       public void register(Toadlet t, String urlPrefix, boolean atFront, 
String name, String title) {
                ToadletElement te = new ToadletElement(t, urlPrefix);
                if(atFront) toadlets.addFirst(te);
                else toadlets.addLast(te);
                t.container = this;
+               if (name != null) {
+                       pageMaker.addNavigationLink(urlPrefix, name, title);
+               }
        }

        public Toadlet findToadlet(URI uri) {
@@ -339,7 +349,7 @@

                public void run() {
                        Logger.minor(this, "Handling connection");
-                       ToadletContextImpl.handle(sock, 
SimpleToadletServer.this, bf);
+                       ToadletContextImpl.handle(sock, 
SimpleToadletServer.this, bf, pageMaker);
                        Logger.minor(this, "Handled connection");
                }


Modified: trunk/freenet/src/freenet/clients/http/Spider.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/Spider.java  2006-08-09 18:24:35 UTC 
(rev 10004)
+++ trunk/freenet/src/freenet/clients/http/Spider.java  2006-08-09 19:01:12 UTC 
(rev 10005)
@@ -36,6 +36,7 @@
 import freenet.plugin.HttpPlugin;
 import freenet.plugin.PluginManager;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -292,39 +293,38 @@
                        return;
                } else if ("list".equals(action)) {
                        String listName = request.getParam("listName", null);
-                       StringBuffer responseBuffer = new StringBuffer();
-                       pageMaker.makeHead(responseBuffer, "The Definitive 
Spider");
+                       HTMLNode pageNode = pageMaker.getPageNode("The 
Definitive Spider");
+                       HTMLNode contentNode = 
pageMaker.getContentNode(pageNode);
                        /* create copies for multi-threaded use */
                        if (listName == null) {
                                Map runningFetches = new 
HashMap(runningFetchesByURI);
                                List queued = new ArrayList(queuedURIList);
                                Set visited = new HashSet(visitedURIs);
                                Set failed = new HashSet(failedURIs);
-                               
responseBuffer.append(createNavbar(runningFetches.size(), queued.size(), 
visited.size(), failed.size()));
-                               responseBuffer.append(createAddBox());
-                               responseBuffer.append(createList("Running 
Fetches", "running", runningFetches.keySet(), maxShownURIs));
-                               responseBuffer.append(createList("Queued URIs", 
"queued", queued, maxShownURIs));
-                               responseBuffer.append(createList("Visited 
URIs", "visited", visited, maxShownURIs));
-                               responseBuffer.append(createList("Failed URIs", 
"failed", failed, maxShownURIs));
+                               contentNode.addChild(createNavbar(pageMaker, 
runningFetches.size(), queued.size(), visited.size(), failed.size()));
+                               contentNode.addChild(createAddBox(pageMaker));
+                               contentNode.addChild(createList(pageMaker, 
"Running Fetches", "running", runningFetches.keySet(), maxShownURIs));
+                               contentNode.addChild(createList(pageMaker, 
"Queued URIs", "queued", queued, maxShownURIs));
+                               contentNode.addChild(createList(pageMaker, 
"Visited URIs", "visited", visited, maxShownURIs));
+                               contentNode.addChild(createList(pageMaker, 
"Failed URIs", "failed", failed, maxShownURIs));
                        } else {
-                               responseBuffer.append(createBackBox());
+                               contentNode.addChild(createBackBox(pageMaker));
                                if ("failed".equals(listName)) {
                                        Set failed = new HashSet(failedURIs);
-                                       
responseBuffer.append(createList("Failed URIs", "failed", failed, -1)); 
+                                       
contentNode.addChild(createList(pageMaker, "Failed URIs", "failed", failed, 
-1));       
                                } else if ("visited".equals(listName)) {
                                        Set visited = new HashSet(visitedURIs);
-                                       
responseBuffer.append(createList("Visited URIs", "visited", visited, -1));
+                                       
contentNode.addChild(createList(pageMaker, "Visited URIs", "visited", visited, 
-1));
                                } else if ("queued".equals(listName)) {
                                        List queued = new 
ArrayList(queuedURIList);
-                                       
responseBuffer.append(createList("Queued URIs", "queued", queued, -1));
+                                       
contentNode.addChild(createList(pageMaker, "Queued URIs", "queued", queued, 
-1));
                                } else if ("running".equals(listName)) {
                                        Map runningFetches = new 
HashMap(runningFetchesByURI);
-                                       
responseBuffer.append(createList("Running Fetches", "running", 
runningFetches.keySet(), -1));
+                                       
contentNode.addChild(createList(pageMaker, "Running Fetches", "running", 
runningFetches.keySet(), -1));
                                }
                        }
-                       pageMaker.makeTail(responseBuffer);
                        MultiValueTable responseHeaders = new MultiValueTable();
-                       byte[] responseBytes = 
responseBuffer.toString().getBytes("utf-8");
+                       byte[] responseBytes = 
pageNode.generate().getBytes("utf-8");
                        context.sendReplyHeaders(200, "OK", responseHeaders, 
"text/html; charset=utf-8", responseBytes.length);
                        context.writeData(responseBytes);
                } else if ("add".equals(action)) {
@@ -338,7 +338,7 @@
                                queueURI(uri);
                                startSomeRequests();
                        } catch (MalformedURLException mue1) {
-                               sendSimpleResponse(context, "URL invalid", "The 
given URI is not valid. Please <a href=\"?action=list\">return</a> and try 
again.");
+                               sendSimpleResponse(context, "URL invalid", "The 
given URI is not valid.");
                                return;
                        }
                        MultiValueTable responseHeaders = new MultiValueTable();
@@ -356,71 +356,63 @@

        private void sendSimpleResponse(ToadletContext context, String title, 
String message) throws ToadletContextClosedException, IOException {
                PageMaker pageMaker = context.getPageMaker();
-               StringBuffer outputBuffer = new StringBuffer();
-               pageMaker.makeHead(outputBuffer, title);
-               outputBuffer.append("<div class=\"infobox infobox-alert\">");
-               outputBuffer.append("<div 
class=\"infobox-header\">").append(HTMLEncoder.encode(title)).append("</div>\n");
-               outputBuffer.append("<div 
class=\"infobox-content\">").append(HTMLEncoder.encode(message)).append("</div>\n");
-               outputBuffer.append("</div>\n");
-               byte[] responseBytes = 
outputBuffer.toString().getBytes("utf-8");
+               HTMLNode pageNode = pageMaker.getPageNode(title);
+               HTMLNode contentNode = pageMaker.getContentNode(pageNode);
+               HTMLNode infobox = 
contentNode.addChild(pageMaker.getInfobox("infobox-alter", title));
+               HTMLNode infoboxContent = pageMaker.getContentNode(infobox);
+               infoboxContent.addChild("#", message);
+               byte[] responseBytes = pageNode.generate().getBytes("utf-8");
                context.sendReplyHeaders(200, "OK", new MultiValueTable(), 
"text/html; charset=utf-8", responseBytes.length);
                context.writeData(responseBytes);
        }

-       private StringBuffer createBackBox() {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-content\">Return to 
the <a href=\"?action=list\">list of all URIs</a>.</div>");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createBackBox(PageMaker pageMaker) {
+               HTMLNode backbox = pageMaker.getInfobox((String) null);
+               HTMLNode backContent = pageMaker.getContentNode(backbox);
+               backContent.addChild("#", "Return to the ");
+               backContent.addChild("a", "href", "?action=list", "list of all 
URIs");
+               backContent.addChild("#", ".");
+               return backbox;
        }

-       private StringBuffer createAddBox() {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div class=\"infobox-header\">Add a 
URI</div>");
-               outputBuffer.append("<div class=\"infobox-content\"><form 
action=\"\" method=\"get\">");
-               outputBuffer.append("<input type=\"hidden\" name=\"action\" 
value=\"add\" />");
-               outputBuffer.append("<input type=\"text\" size=\"40\" 
name=\"key\" value=\"\" />");
-               outputBuffer.append("<input type=\"submit\" value=\"Add URI\" 
/>");
-               outputBuffer.append("</form></div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createAddBox(PageMaker pageMaker) {
+               HTMLNode addBox = pageMaker.getInfobox("Add a URI");
+               HTMLNode formNode = pageMaker.getContentNode(addBox);
+               formNode.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "action", "add" });
+               formNode.addChild("input", new String[] { "type", "size", 
"name", "value" }, new String[] { "text", "40", "key", "" });
+               formNode.addChild("input", new String[] { "type", "value" }, 
new String[] { "submit", "Add URI" });
+               return addBox;
        }

-       private StringBuffer createNavbar(int running, int queued, int visited, 
int failed) {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<div class=\"infobox navbar\">");
-               outputBuffer.append("<div class=\"infobox-header\">Page 
navigation</div>");
-               outputBuffer.append("<div class=\"infobox-content\"><ul>");
-               outputBuffer.append("<li><a href=\"#running\">Running 
(").append(running).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#queued\">Queued 
(").append(queued).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#visited\">Visited 
(").append(visited).append(")</a></li>");
-               outputBuffer.append("<li><a href=\"#failed\">Failed 
(").append(failed).append(")</a></li>");
-               outputBuffer.append("</ul></div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+       private HTMLNode createNavbar(PageMaker pageMaker, int running, int 
queued, int visited, int failed) {
+               HTMLNode navbar = pageMaker.getInfobox("navbar", "Page 
Navigation");
+               HTMLNode list = pageMaker.getContentNode(navbar).addChild("ul");
+               list.addChild("li").addChild("a", "href", "#running", "Running 
(" + running + ")");
+               list.addChild("li").addChild("a", "href", "#queued", "Queued (" 
+ queued + ")");
+               list.addChild("li").addChild("a", "href", "#visited", "Visited 
(" + visited + ")");
+               list.addChild("li").addChild("a", "href", "#failed", "Failed (" 
+ failed + ")");
+               return navbar;
        }

-       private StringBuffer createList(String listName, String anchorName, 
Collection collection, int maxCount) {
-               StringBuffer outputBuffer = new StringBuffer();
-               outputBuffer.append("<a 
name=\"").append(HTMLEncoder.encode(anchorName)).append("\"></a>");
-               outputBuffer.append("<div class=\"infobox\">");
-               outputBuffer.append("<div 
class=\"infobox-header\">").append(HTMLEncoder.encode(listName)).append(" 
(").append(collection.size()).append(")</div>\n");
-               outputBuffer.append("<div class=\"infobox-content\">");
+       private HTMLNode createList(PageMaker pageMaker, String listName, 
String anchorName, Collection collection, int maxCount) {
+               HTMLNode listNode = new HTMLNode("div");
+               listNode.addChild("a", "name", anchorName);
+               HTMLNode listBox = pageMaker.getInfobox(listName);
+               HTMLNode listContent = pageMaker.getContentNode(listBox);
+               listNode.addChild(listBox);
                Iterator collectionItems = collection.iterator();
                int itemCount = 0;
                while (collectionItems.hasNext()) {
                        FreenetURI uri = (FreenetURI) collectionItems.next();
-                       
outputBuffer.append(HTMLEncoder.encode(uri.toString())).append("<br/>\n");
+                       listContent.addChild("#", uri.toString(false));
+                       listContent.addChild("br");
                        if (itemCount++ == maxCount) {
-                               outputBuffer.append("<br/><a 
href=\"?action=list&amp;listName=").append(HTMLEncoder.encode(anchorName)).append("\">Show
 all&hellip;</a>");
+                               listContent.addChild("br");
+                               listContent.addChild("a", "href", 
"?action=list&listName=" + anchorName, "Show all\u2026");
                                break;
                        }
                }
-               outputBuffer.append("</div>\n");
-               outputBuffer.append("</div>\n");
-               return outputBuffer;
+               return listNode;
        }

        /**

Modified: trunk/freenet/src/freenet/clients/http/Toadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/Toadlet.java 2006-08-09 18:24:35 UTC 
(rev 10004)
+++ trunk/freenet/src/freenet/clients/http/Toadlet.java 2006-08-09 19:01:12 UTC 
(rev 10005)
@@ -11,10 +11,9 @@
 import freenet.client.InserterException;
 import freenet.keys.FreenetURI;
 import freenet.support.HTMLEncoder;
-import freenet.support.Logger;
+import freenet.support.HTMLNode;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
-import freenet.support.io.BucketTools;

 /**
  * Replacement for servlets. Just an easy to use HTTP interface, which is
@@ -50,50 +49,35 @@
         * @throws ToadletContextClosedException 
         */
        public void handleGet(URI uri, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {
-               StringBuffer buf = new StringBuffer();
-               
-               ctx.getPageMaker().makeHead(buf, "Not supported");
-               
-               buf.append("Operation not supported");
-               ctx.getPageMaker().makeTail(buf);
-               
-               MultiValueTable hdrtbl = new MultiValueTable();
-               hdrtbl.put("Allow", this.supportedMethods());
-               ctx.sendReplyHeaders(405, "Operation not Supported", hdrtbl, 
"text/html", buf.length());
-               ctx.writeData(buf.toString().getBytes(), 0, buf.length());
+               handleUnhandledRequest(uri, null, ctx);
        }

-
        /**
         * Likewise for a PUT request.
         */
        public void handlePut(URI uri, Bucket data, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {
-               StringBuffer buf = new StringBuffer();
-               
-               ctx.getPageMaker().makeHead(buf, "Not supported");
-               
-               buf.append("Operation not supported");
-               ctx.getPageMaker().makeTail(buf);
-               
-               MultiValueTable hdrtbl = new MultiValueTable();
-               hdrtbl.put("Allow", this.supportedMethods());
-               ctx.sendReplyHeaders(405, "Operation not Supported", hdrtbl, 
"text/html", buf.length());
-               ctx.writeData(buf.toString().getBytes(), 0, buf.length());
+               handleUnhandledRequest(uri, null, ctx);
        }

        public void handlePost(URI uri, Bucket data, ToadletContext ctx) throws 
ToadletContextClosedException, IOException, RedirectException {
-               StringBuffer buf = new StringBuffer();
-               
-               ctx.getPageMaker().makeHead(buf, "Not supported");
-               
-               buf.append("Operation not supported");
-               ctx.getPageMaker().makeTail(buf);
-               
+               handleUnhandledRequest(uri, null, ctx);
+       }
+       
+       private void handleUnhandledRequest(URI uri, Bucket data, 
ToadletContext toadletContext) throws ToadletContextClosedException, 
IOException, RedirectException {
+               HTMLNode pageNode = 
toadletContext.getPageMaker().getPageNode("Not supported");
+               HTMLNode contentNode = 
toadletContext.getPageMaker().getContentNode(pageNode);
+
+               HTMLNode infobox = contentNode.addChild("div", "class", 
"infobox infobox-error");
+               infobox.addChild("div", "class", "infobox-header", "Not 
supported");
+               infobox.addChild("div", "class", "infobox-content", "Your 
browser sent a request that Freenet could not understand.");
+
                MultiValueTable hdrtbl = new MultiValueTable();
                hdrtbl.put("Allow", this.supportedMethods());
-               ctx.sendReplyHeaders(405, "Operation not Supported", hdrtbl, 
"text/html", buf.length());
-               ctx.writeData(buf.toString().getBytes(), 0, buf.length());
-               Logger.minor(this, "POSTed data to 
"+uri+":\n"+BucketTools.toByteArray(data));
+
+               StringBuffer pageBuffer = new StringBuffer();
+               pageNode.generate(pageBuffer);
+               toadletContext.sendReplyHeaders(405, "Operation not Supported", 
hdrtbl, "text/html; charset=utf-8", pageBuffer.length());
+               toadletContext.writeData(pageBuffer.toString().getBytes());
        }

        /**
@@ -176,21 +160,16 @@
         * Send a simple error page.
         */
        protected void sendErrorPage(ToadletContext ctx, int code, String desc, 
String message) throws ToadletContextClosedException, IOException {
-               StringBuffer buf = new StringBuffer();
-                       
-               ctx.getPageMaker().makeHead(buf, desc);
-               //
-               buf.append("<div class=\"infobox infobox-error\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append(desc);
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append(message);
-               //
-               buf.append("</div>\n");
-               buf.append("</div>\n");
-               ctx.getPageMaker().makeTail(buf);
-               writeReply(ctx, code, "text/html; charset=UTF-8", desc, 
buf.toString());
+               HTMLNode pageNode = ctx.getPageMaker().getPageNode(desc);
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+               
+               HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-error", desc));
+               HTMLNode infoboxContent = 
ctx.getPageMaker().getContentNode(infobox);
+               infoboxContent.addChild("#", message);
+               infoboxContent.addChild("br");
+               infoboxContent.addChild("a", "href", ".", "Return to Peers 
page.");
+               
+               writeReply(ctx, code, "text/html; charset=UTF-8", desc, 
pageNode.generate());
        }

        /**

Modified: trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java      
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java      
2006-08-09 19:01:12 UTC (rev 10005)
@@ -38,12 +38,12 @@
         */
        private boolean closed;

-       public ToadletContextImpl(Socket sock, MultiValueTable headers, String 
CSSName, BucketFactory bf) throws IOException {
+       public ToadletContextImpl(Socket sock, MultiValueTable headers, String 
CSSName, BucketFactory bf, PageMaker pageMaker) throws IOException {
                this.headers = headers;
                this.closed = false;
                sockOutputStream = sock.getOutputStream();
                this.bf = bf;
-               pagemaker = new PageMaker(CSSName);
+               this.pagemaker = pageMaker;
        }

        private void close() {
@@ -164,7 +164,7 @@
        /**
         * Handle an incoming connection. Blocking, obviously.
         */
-       public static void handle(Socket sock, ToadletContainer container, 
BucketFactory bf) {
+       public static void handle(Socket sock, ToadletContainer container, 
BucketFactory bf, PageMaker pageMaker) {
                try {
                        InputStream is = sock.getInputStream();

@@ -228,7 +228,7 @@

                                boolean shouldDisconnect = 
shouldDisconnectAfterHandled(split[2].equals("HTTP/1.0"), headers);

-                               ToadletContextImpl ctx = new 
ToadletContextImpl(sock, headers, container.getCSSName(), bf);
+                               ToadletContextImpl ctx = new 
ToadletContextImpl(sock, headers, container.getCSSName(), bf, pageMaker);

                                /*
                                 * if we're handling a POST, copy the data into 
a bucket now,

Modified: trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java  2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java  2006-08-09 
19:01:12 UTC (rev 10005)
@@ -16,7 +16,7 @@
 import freenet.node.NodeStarter;
 import freenet.node.Version;
 import freenet.node.useralerts.UserAlert;
-import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.MultiValueTable;
 import freenet.support.io.Bucket;
@@ -45,8 +45,6 @@
                HTTPRequest request = new HTTPRequest(uri,data,ctx);
                if(request==null) return;

-               StringBuffer buf = new StringBuffer();
-               
                if (request.getParam("shutdownconfirm").length() > 0) {
                        MultiValueTable headers = new MultiValueTable();
                        headers.put("Location", 
".?shutdownconfirm="+node.formPassword.hashCode());
@@ -61,74 +59,49 @@
                        return;
                }else if(request.getParam("updateconfirm").length() > 0){
                        // false for no navigation bars, because that would be 
very silly
-                       ctx.getPageMaker().makeHead(buf, "Node Updating", true);
-                       buf.append("<div class=\"infobox 
infobox-information\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("The Freenet node is being updated and will 
self-restart\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("The restart process might take up to 10 
minutes. <br>");
-                       buf.append("(The node will try to fetch a revocation 
key before updating)<br>");
-                       buf.append("Thank you for using Freenet\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node updating");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-information", "Node 
updating"));
+                       HTMLNode content = 
ctx.getPageMaker().getContentNode(infobox);
+                       content.addChild("p").addChild("#", "The Freenet node 
is being updated will self-restart. The restart process might take up to 10 
minutes, because the node will try to fetch a revocation key before updating.");
+                       content.addChild("p").addChild("#", "Thank you for 
using Freenet.");
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        Logger.normal(this, "Node is updating/restarting");
                        node.ps.queueTimedJob(new Runnable() {
                                public void run() { 
node.getNodeUpdater().Update(); }}, 0);
                        return;
                }else if (request.getParam("restart").length() > 0) {
-                       ctx.getPageMaker().makeHead(buf, "Node Restart");
-                       buf.append("<div class=\"infobox infobox-query\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Node Restart?\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("Are you sure you wish to restart your 
Freenet node?\n");
-                       buf.append("<form action=\"/\" method=\"post\">\n");
-                       buf.append("<input type=\"submit\" name=\"cancel\" 
value=\"Cancel\" />\n");
-                       buf.append("<input type=\"submit\" 
name=\"restartconfirm\" value=\"Restart\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node Restart");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-query", "Node 
Restart"));
+                       HTMLNode content = 
ctx.getPageMaker().getContentNode(infobox);
+                       content.addChild("p").addChild("#", "Are you sure you 
want to restart your Freenet node?");
+                       HTMLNode restartForm = 
content.addChild("p").addChild("form", new String[] { "action", "method" }, new 
String[] { "/", "post" });
+                       restartForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "cancel", "Cancel" });
+                       restartForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "restartconfirm", "Restart" });
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                }else if (request.getParam("update").length() > 0) {
-                       ctx.getPageMaker().makeHead(buf, "Node Update");
-                       buf.append("<div class=\"infobox infobox-query\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Update the node?\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("Are you sure you wish to update your 
Freenet node?\n");
-                       buf.append("<form action=\"/\" method=\"post\">\n");
-                       buf.append("<input type=\"submit\" name=\"cancel\" 
value=\"Cancel\" />\n");
-                       buf.append("<input type=\"submit\" 
name=\"updateconfirm\" value=\"Update\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node Update");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-query", "Node 
Update"));
+                       HTMLNode content = 
ctx.getPageMaker().getContentNode(infobox);
+                       content.addChild("p").addChild("#", "Are you sure you 
wish to update your Freenet node?");
+                       HTMLNode updateForm = 
content.addChild("p").addChild("form", new String[] { "action", "method" }, new 
String[] { "/", "post" });
+                       updateForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "cancel", "Cancel" });
+                       updateForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "updateconfirm", "Update" });
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                } else if (request.getParam("exit").equalsIgnoreCase("true")) {
-                       ctx.getPageMaker().makeHead(buf, "Node Shutdown");
-                       buf.append("<div class=\"infobox infobox-query\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Node Shutdown?\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("Are you sure you wish to shut down your 
Freenet node?\n");
-                       buf.append("<form action=\"/\" method=\"post\">\n");
-                       buf.append("<input type=\"submit\" name=\"cancel\" 
value=\"Cancel\" />\n");
-                       buf.append("<input type=\"submit\" 
name=\"shutdownconfirm\" value=\"Shut down\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node Shutdown");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-query", "Node 
Shutdown"));
+                       HTMLNode content = 
ctx.getPageMaker().getContentNode(infobox);
+                       content.addChild("p").addChild("#", "Are you sure you 
wish to shut down your Freenet node?");
+                       HTMLNode shutdownForm = 
content.addChild("p").addChild("form", new String[] { "action", "method" }, new 
String[] { "/", "post" });
+                       shutdownForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "cancel", "Cancel" });
+                       shutdownForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "shutdownconfirm", "Shut down" });
+                       writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                } else if (request.isParameterSet("addbookmark")) {
                        try {
@@ -201,39 +174,36 @@

                                Bucket bucket = request.getPart("filename");

+                               HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Insertion");
+                               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                               HTMLNode content;
                                InsertBlock block = new InsertBlock(bucket, 
contentType, key);
                                try {
-                                       ctx.getPageMaker().makeHead(buf, 
"Insertion");
                                        key = this.insert(block, false);
-                                       buf.append("<div class=\"infobox 
infobox-success\">\n");
-                                       buf.append("<div 
class=\"infobox-header\">\n");
-                                       buf.append("Insert Succeeded\n");
-                                       buf.append("</div>\n");
-                                       buf.append("<div 
class=\"infobox-content\">\n");
-                                       buf.append("The key : <a href=\"/" + 
key.getKeyType() + "@" + key.getGuessableKey() + "\">" +
-                                                       key.getKeyType() + "@" 
+ key.getGuessableKey() +"</a> has been inserted successfully.<br>");
+                                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-success", "Insert 
Succeeded"));
+                                       content = 
ctx.getPageMaker().getContentNode(infobox);
+                                       content.addChild("#", "The key ");
+                                       content.addChild("a", "href", "/" + 
key.getKeyType() + "@" + key.getGuessableKey(), key.getKeyType() + "@" + 
key.getGuessableKey());
+                                       content.addChild("#", " has been 
inserted successfully.");
                                } catch (InserterException e) {
-                                       buf.append("<div class=\"infobox 
infobox-error\">\n");
-                                       buf.append("<div 
class=\"infobox-header\">\n");
-                                       buf.append("Insert Failed\n");
-                                       buf.append("</div>\n");
-                                       buf.append("<div 
class=\"infobox-content\">\n");
-                                       buf.append("Error: 
"+e.getMessage()+"<br>");
-                                       if(e.uri != null)
-                                               buf.append("URI would have 
been: "+e.uri+"<br>");
+                                       HTMLNode infobox = 
ctx.getPageMaker().getInfobox("infobox-error", "Insert Failed");
+                                       content = 
ctx.getPageMaker().getContentNode(infobox);
+                                       content.addChild("#", "The insert 
failed with the message: " + e.getMessage());
+                                       content.addChild("br");
+                                       if (e.uri != null) {
+                                               content.addChild("#", "The URI 
would have been: " + e.uri);
+                                       }
                                        int mode = e.getMode();
                                        if((mode == 
InserterException.FATAL_ERRORS_IN_BLOCKS) || (mode == 
InserterException.TOO_MANY_RETRIES_IN_BLOCKS)) {
-                                               buf.append("Splitfile-specific 
error:\n"+e.errorCodes.toVerboseString()+"<br>");
+                                               content.addChild("br"); /* TODO 
*/
+                                               content.addChild("#", 
"Splitfile-specific error: " + e.errorCodes.toVerboseString());
                                        }
                                }
+
+                               content.addChild("br");
+                               content.addChild("a", new String[] { "href", 
"title" }, new String[] { "/", "Node Homepage" }, "Homepage");

-                               ctx.getPageMaker().makeBackLink(buf,ctx);
-                               buf.append("<br><a href=\"/\" title=\"Node 
Homepage\">Homepage</a>\n");
-                               buf.append("</div>\n");
-                               buf.append("</div>\n");
-                               
-                               ctx.getPageMaker().makeTail(buf);
-                               writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                               writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                                request.freeParts();
                                bucket.free();
                }else {
@@ -242,75 +212,45 @@
        }

        public void handleGet(URI uri, ToadletContext ctx) throws 
ToadletContextClosedException, IOException {
-               StringBuffer buf = new StringBuffer();
-               
                boolean advancedDarknetOutputEnabled = 
node.getToadletContainer().isAdvancedDarknetEnabled();

                HTTPRequest request = new HTTPRequest(uri);
                if (request.getParam("newbookmark").length() > 0) {
-                       ctx.getPageMaker().makeHead(buf, "Add A Bookmark");
-                       
-                       buf.append("<div class=\"infobox infobox-query\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Confirm action\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("<form action=\".\" method=\"post\">\n");
-                       buf.append("Please confirm that you wish to add the 
key:<br />\n");
-                       
buf.append("<i>"+request.getParam("newbookmark")+"</i><br />");
-                       buf.append("To your bookmarks, and enter the 
description that you would prefer:<br />\n");
-                       buf.append("Description:\n");
-                       buf.append("<input type=\"text\" name=\"name\" 
value=\""+HTMLEncoder.encode(request.getParam("desc"))+"\" style=\"width: 100%; 
\" />\n");
-                       buf.append("<input type=\"hidden\" name=\"key\" 
value=\""+HTMLEncoder.encode(request.getParam("newbookmark"))+"\" />\n");
-                       buf.append("<input type=\"submit\" name=\"addbookmark\" 
value=\"Add bookmark\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       
-                       ctx.getPageMaker().makeTail(buf);
-               
-                       this.writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                       HTMLNode pageNode = ctx.getPageMaker().getPageNode("Add 
a Bookmark");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("Confirm Bookmark 
Addition"));
+                       HTMLNode addForm = 
ctx.getPageMaker().getContentNode(infobox).addChild("form", new String[] { 
"action", "method" }, new String[] { ".", "post" });
+                       addForm.addChild("#", "Please confirm that you want to 
add the key " + request.getParam("newbookmark") + " to your bookmarks and enter 
the description that you would prefer:");
+                       addForm.addChild("br");
+                       addForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "hidden", "key", 
request.getParam("newbookmark") });
+                       addForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "text", "name", request.getParam("desc") });
+                       addForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "addbookmark", "Add bookmark" });
+                       this.writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                } else if (request.isParameterSet("managebookmarks")) {
-                       ctx.getPageMaker().makeHead(buf, "Bookmark Manager");
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Bookmark Manager");
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-normal", "My 
Bookmarks"));
+                       HTMLNode infoboxContent = 
ctx.getPageMaker().getContentNode(infobox);

-                       // existing bookmarks
-                       buf.append("<div class=\"infobox infobox-normal\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("My Bookmarks\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("<form action=\".\" method=\"post\">\n");
                        Enumeration e = bookmarks.getBookmarks();
                        if (!e.hasMoreElements()) {
-                               buf.append("<i>You currently have no bookmarks 
defined</i>");
+                               infoboxContent.addChild("#", "You currently do 
not have any bookmarks defined.");
                        } else {
-                               buf.append("<ul id=\"bookmarks\">\n");
+                               HTMLNode manageForm = 
infoboxContent.addChild("form", new String[] { "action", "method" }, new 
String[] { ".", "post" });
+                               HTMLNode bookmarkList = 
manageForm.addChild("ul", "id", "bookmarks");
                                while (e.hasMoreElements()) {
                                        Bookmark b = (Bookmark)e.nextElement();

-                                       buf.append("<li style=\"clear: right; 
\">\n");
-                                       buf.append("<input type=\"submit\" 
name=\"delete_"+b.hashCode()+"\" value=\"Delete\" style=\"float: right; \" 
/>\n");
-                                       buf.append("<input type=\"submit\" 
name=\"edit_"+b.hashCode()+"\" value=\"Edit\" style=\"float: right; \" />\n");
-                                       buf.append("<a 
href=\"/"+HTMLEncoder.encode(b.getKey())+"\">");
-                                       
buf.append(HTMLEncoder.encode(b.getDesc()));
-                                       buf.append("</a>\n");
-
-                                       buf.append("</li>\n");
+                                       HTMLNode bookmark = 
bookmarkList.addChild("li", "style", "clear: right;"); /* TODO */
+                                       bookmark.addChild("input", new String[] 
{ "type", "name", "value", "style" }, new String[] { "submit", "delete_" + 
b.hashCode(), "Delete", "float: right;" });
+                                       bookmark.addChild("input", new String[] 
{ "type", "name", "value", "style" }, new String[] { "submit", "edit_" + 
b.hashCode(), "Edit", "float: right;" });
+                                       bookmark.addChild("a", "href", "/" + 
b.getKey(), b.getDesc());
                                }
-                               buf.append("</ul>\n");
+                               manageForm.addChild("input", new String[] { 
"type", "name", "value" }, new String[] { "hidden", "managebookmarks", "yes" });
                        }
-                       buf.append("<input type=\"hidden\" 
name=\"managebookmarks\" value=\"yes\" />\n");
-                       buf.append("</form>\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       
-                       // new bookmark
-                       this.makeBookmarkEditForm(buf, MODE_ADD, null, "", "", 
null);
-                       
-                       ctx.getPageMaker().makeTail(buf);
-               
-                       this.writeReply(ctx, 200, "text/html", "OK", 
buf.toString());
+                       
contentNode.addChild(createBookmarkEditForm(ctx.getPageMaker(), MODE_ADD, null, 
"", ""));
+                       this.writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
                        return;
                }else if (request.getParam("shutdownconfirm").length() > 0) {
                        if(request.getIntParam("shutdownconfirm") != 
node.formPassword.hashCode()){
@@ -319,18 +259,12 @@
                                ctx.sendReplyHeaders(302, "Found", headers, 
null, 0);
                                return;
                        }
-                       // false for no navigation bars, because that would be 
very silly
-                       ctx.getPageMaker().makeHead(buf, "Node Shutdown", 
false);
-                       buf.append("<div class=\"infobox 
infobox-information\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("The Freenet node has been successfully shut 
down\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("Thank you for using Freenet\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node Shutdown", false);
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-information", "The 
Freenet node has been successfully shut down."));
+                       HTMLNode infoboxContent = 
ctx.getPageMaker().getContentNode(infobox);
+                       infoboxContent.addChild("#", "Thank you for using 
Freenet.");
+                       writeReply(ctx, 200, "text/html; charset=utf-8", "OK", 
pageNode.generate());
                        return;
                }else if(request.getParam("restartconfirm").length() > 0){
                        if(request.getIntParam("restartconfirm") != 
node.formPassword.hashCode()){
@@ -340,34 +274,23 @@
                                return;
                        }
                        // false for no navigation bars, because that would be 
very silly
-                       ctx.getPageMaker().makeHead(buf, "Node Restart", false);
-                       buf.append("<div class=\"infobox 
infobox-information\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("The Freenet node is beeing restarted\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("The restart process might take up to 3 
minutes. <br>");
-                       buf.append("Thank you for using Freenet\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
-                       ctx.getPageMaker().makeTail(buf);
-                       
-                       writeReply(ctx, 200, "text/html", "OK", buf.toString());
+                       HTMLNode pageNode = 
ctx.getPageMaker().getPageNode("Node Restart", false);
+                       HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+                       HTMLNode infobox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-information", "The 
Freenet is being restarted."));
+                       HTMLNode infoboxContent = 
ctx.getPageMaker().getContentNode(infobox);
+                       infoboxContent.addChild("#", "Please wait while the 
node is being restarted. This might take up to 3 minutes. Thank you for using 
Freenet.");
+                       writeReply(ctx, 200, "text/html; charset=utf-8", "OK", 
pageNode.generate());
                        Logger.normal(this, "Node is restarting");
                        return;
                }

-               
-               ctx.getPageMaker().makeHead(buf, "Freenet FProxy Homepage of 
"+node.getMyName());
+               HTMLNode pageNode = ctx.getPageMaker().getPageNode("Freenet 
FProxy Homepage of " + node.getMyName());
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);
+
                if(node.isTestnetEnabled()) {
-                       buf.append("<div class=\"infobox infobox-alert\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("Testnet mode!\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append("This node runs in testnet mode. This WILL 
seriously jeopardize your anonymity!\n");
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
+                       HTMLNode testnetBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-alert", "Testnet 
Mode!"));
+                       HTMLNode testnetContent = 
ctx.getPageMaker().getContentNode(testnetBox);
+                       testnetContent.addChild("#", "This node runs in testnet 
mode. This WILL seriously jeopardize your anonymity!");
                }

                String useragent = (String)ctx.getHeaders().get("user-agent");
@@ -375,106 +298,71 @@
                if (useragent != null) {
                        useragent = useragent.toLowerCase();
                        if ((useragent.indexOf("msie") > -1) && 
(useragent.indexOf("opera") == -1)) {
-                               buf.append("<div class=\"infobox 
infobox-alert\">\n");
-                               buf.append("<div class=\"infobox-header\">\n");
-                               buf.append("Security risk!\n");
-                               buf.append("</div>\n");
-                               buf.append("<div class=\"infobox-content\">\n");
-                               buf.append("You appear to be using Internet 
Explorer. This means that some sites within Freenet may be able to compromise 
your anonymity!\n");
-                               buf.append("</div>\n");
-                               buf.append("</div>\n");
+                               HTMLNode browserWarningBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-alert", "Security 
Risk!"));
+                               HTMLNode browserWarningContent = 
ctx.getPageMaker().getContentNode(browserWarningBox);
+                               browserWarningContent.addChild("#", "You appear 
to be using Microsoft Internet Explorer. This means that some sites within 
Freenet may be able to compromise your anonymity!");
                        }
                }

                // Alerts
+               contentNode.addChild(node.alerts.createAlerts());

-               node.alerts.toHtml(buf);
-               
                // Fetch-a-key box
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Fetch a Key\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\" 
id=\"keyfetchbox\">\n");
-               buf.append("<form action=\"/\" method=\"get\">\n");
-               buf.append("Key: <input type=\"text\" size=\"80\" 
name=\"key\"/>\n");
-               buf.append("<input type=\"submit\" value=\"Fetch\" />\n");
-               buf.append("</form>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               HTMLNode fetchKeyBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-normal", "Fetch a 
Key"));
+               HTMLNode fetchKeyContent = 
ctx.getPageMaker().getContentNode(fetchKeyBox);
+               fetchKeyContent.addAttribute("id", "keyfetchbox");
+               HTMLNode fetchKeyForm = fetchKeyContent.addChild("form", new 
String[] { "action", "method" }, new String[] { "/", "get" });
+               fetchKeyForm.addChild("#", "Key: ");
+               fetchKeyForm.addChild("input", new String[] { "type", "size", 
"name" }, new String[] { "text", "80", "key" });
+               fetchKeyForm.addChild("input", new String[] { "type", "value" 
}, new String[] { "submit", "Fetch" });

                // Bookmarks
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("My Bookmarks\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
+               HTMLNode bookmarkBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-normal", "My 
Bookmarks"));
+               HTMLNode bookmarkContent = 
ctx.getPageMaker().getContentNode(bookmarkBox);

                Enumeration e = bookmarks.getBookmarks();
                if (!e.hasMoreElements()) {
-                       buf.append("<i>You currently have no bookmarks 
defined</i>");
+                       bookmarkContent.addChild("#", "You currently do not 
have any bookmarks defined.");
                } else {
-                       buf.append("<ul id=\"bookmarks\">\n");
+                       HTMLNode bookmarkList = bookmarkContent.addChild("ul", 
"id", "bookmarks");
                        while (e.hasMoreElements()) {
                                Bookmark b = (Bookmark)e.nextElement();
-                               
-                               buf.append("<li><a 
href=\"/"+HTMLEncoder.encode(b.getKey())+"\">");
-                               buf.append(HTMLEncoder.encode(b.getDesc()));
-                               buf.append("</a></li>\n");
+                               bookmarkList.addChild("li").addChild("a", 
"href", "/" + b.getKey(), b.getDesc());
                        }
-                       buf.append("</ul>\n");
                }
-               buf.append("<div id=\"bookmarkedit\">\n");
-               buf.append("<a href=\"?managebookmarks\" 
class=\"interfacelink\">Edit My Bookmarks</a>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               bookmarkContent.addChild("div", "id", 
"bookmarkedit").addChild("a", new String[] { "href", "class" }, new String[] { 
"?managebookmarks", "interfacelink" }, "Edit my bookmarks");

                // Version info and Quit Form
-               buf.append("<div class=\"infobox infobox-information\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Version\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               
-               buf.append("Freenet "+Version.nodeVersion+" Build 
#"+Version.buildNumber()+" r"+Version.cvsRevision+"<br/>");
-               buf.append("Freenet-ext Build #"+NodeStarter.extBuildNumber+" 
r"+NodeStarter.extRevisionNumber+"<br/>");         
-        
+               HTMLNode versionBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-information", 
"Version Information & Node Control"));
+               HTMLNode versionContent = 
ctx.getPageMaker().getContentNode(versionBox);
+               versionContent.addChild("#", "Freenet " + Version.nodeVersion + 
" Build #" + Version.buildNumber() + " r" + Version.cvsRevision);
+               versionContent.addChild("br");
+               versionContent.addChild("#", "Freenet-ext Build #" + 
NodeStarter.extBuildNumber + " r" + NodeStarter.extRevisionNumber);
+               versionContent.addChild("br");
                if((Version.buildNumber() < Version.highestSeenBuild) && 
advancedDarknetOutputEnabled) {
-                       buf.append("<br />");
-                       buf.append("<b>A newer version is available! (Build 
#"+Version.highestSeenBuild+")</b>");
+                       versionContent.addChild("b", "A newer version is 
available! (Build #" + Version.highestSeenBuild + ")");
+                       versionContent.addChild("br");
                }
-               buf.append("<form method=\"post\" action=\".\">\n");
-               buf.append("<input type=\"hidden\" name=\"exit\" value=\"true\" 
/><input type=\"submit\" value=\"Shut down the node\" />\n");
-               buf.append("</form>");
+               HTMLNode shutdownForm = versionContent.addChild("form", new 
String[] { "action", "method" }, new String[] { ".", "post" });
+               shutdownForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "exit", "true" });
+               shutdownForm.addChild("input", new String[] { "type", "value" 
}, new String[] { "submit", "Shutdown the node" });
                if(node.isUsingWrapper()){
-                       buf.append("<form action=\"/\" method=\"post\">\n");
-                       buf.append("<input type=\"submit\" name=\"restart\" 
value=\"Restart the node\" />\n");
-                       buf.append("</form>");
+                       HTMLNode restartForm = versionContent.addChild("form", 
new String[] { "action", "method" }, new String[] { ".", "post" });
+                       restartForm.addChild("input", new String[] { "type", 
"name", "value" }, new String[] { "submit", "restart", "Restart the node" });
                }
-               buf.append("\n</div>\n");
-               buf.append("</div>\n");

                // Activity
-               buf.append("<div class=\"infobox infobox-information\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Current Activity\n");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append("<ul id=\"activity\">\n");
-               buf.append("<li>Inserts: "+this.node.getNumInserts()+"</li>\n");
-               buf.append("<li>Requests: 
"+this.node.getNumRequests()+"</li>\n");
-               buf.append("<li>Transferring Requests: 
"+this.node.getNumTransferringRequests()+"</li>\n");
-               if(advancedDarknetOutputEnabled) {
-                       buf.append("<li>ARK Fetch Requests: 
"+this.node.getNumARKFetchers()+"</li>\n");
+               HTMLNode activityBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-information", 
"Current Activity"));
+               HTMLNode activityContent = 
ctx.getPageMaker().getContentNode(activityBox);
+               HTMLNode activityList = activityContent.addChild("ul", "id", 
"activity");
+               activityList.addChild("li", "Inserts: " + node.getNumInserts());
+               activityList.addChild("li", "Requests: " + 
node.getNumRequests());
+               activityList.addChild("li", "Transferring Requests: " + 
node.getNumTransferringRequests());
+               if (advancedDarknetOutputEnabled) {
+                       activityList.addChild("li", "ARK Fetch Requests: " + 
node.getNumARKFetchers());
                }
-               buf.append("</ul>\n");
-               buf.append("</div>\n");
-               buf.append("</div>\n");

-               ctx.getPageMaker().makeTail(buf);
-               
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+               this.writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
        }

        private void sendBookmarkEditPage(ToadletContext ctx, Bookmark b) 
throws ToadletContextClosedException, IOException {
@@ -482,66 +370,37 @@
        }

        private void sendBookmarkEditPage(ToadletContext ctx, int mode, 
Bookmark b, String origKey, String origDesc, String message) throws 
ToadletContextClosedException, IOException {
-               StringBuffer buf = new StringBuffer();
+               HTMLNode pageNode = ctx.getPageMaker().getPageNode((mode == 
MODE_ADD) ? "Add a Bookmark" : "Edit a Bookmark");
+               HTMLNode contentNode = 
ctx.getPageMaker().getContentNode(pageNode);

-               if (mode == MODE_ADD) {
-                       ctx.getPageMaker().makeHead(buf, "Add a Bookmark");
-               } else {
-                       ctx.getPageMaker().makeHead(buf, "Edit a Bookmark");
-               }
-               
                if (message != null) {  // only used for error messages so 
far...
-                       buf.append("<div class=\"infobox infobox-error\">\n");
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append("An Error Occured\n");
-                       buf.append("</div>\n");
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append(message);
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
+                       HTMLNode errorBox = 
contentNode.addChild(ctx.getPageMaker().getInfobox("infobox-error", "An Error 
Occured"));
+                       
ctx.getPageMaker().getContentNode(errorBox).addChild("#", message);
                }

-               this.makeBookmarkEditForm(buf, mode, b, origKey, origDesc, 
message);
+               contentNode.addChild(createBookmarkEditForm(ctx.getPageMaker(), 
mode, b, origKey, origDesc));

-               ctx.getPageMaker().makeTail(buf);
-               this.writeReply(ctx, 200, "text/html", "OK", buf.toString());
+               this.writeReply(ctx, 200, "text/html", "OK", 
pageNode.generate());
        }

-       private void makeBookmarkEditForm(StringBuffer buf, int mode, Bookmark 
b, String origKey, String origDesc, String message) {
-               buf.append("<div class=\"infobox infobox-normal\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
+       private HTMLNode createBookmarkEditForm(PageMaker pageMaker, int mode, 
Bookmark b, String origKey, String origDesc) {
+               HTMLNode infobox = pageMaker.getInfobox("infobox-normal 
bookmark-edit", (mode == MODE_ADD) ? "New Bookmark" : "Update Bookmark");
+               HTMLNode content = pageMaker.getContentNode(infobox);
+               HTMLNode editForm = content.addChild("form", new String[] { 
"action", "method" }, new String[] { ".", "post" });
+               editForm.addChild("#", "Key: ");
+               editForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "text", "key", origKey });
+               editForm.addChild("br");
+               editForm.addChild("#", "Description: ");
+               editForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "text", "name", origDesc });
+               editForm.addChild("br");
                if (mode == MODE_ADD) {
-                       buf.append("New Bookmark\n");
+                       editForm.addChild("input", new String[] { "type", 
"name", "value", "class" }, new String[] { "submit", "addbookmark", "Add 
bookmark", "confirm" });
                } else {
-                       buf.append("Update Bookmark\n");
+                       editForm.addChild("input", new String[] { "type", 
"name", "value", "class" }, new String[] { "submit", "update_" + b.hashCode(), 
"Update bookmark", "confirm" });
                }
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               
-               buf.append("<form action=\".\" method=\"post\">\n");
-               buf.append("<div style=\"text-align: right; \">\n");
-               
-               buf.append("Key: \n");
-               buf.append("<input type=\"text\" name=\"key\" 
value=\""+origKey+"\" size=\"80\" />\n");
-               buf.append("<br />\n");
-               
-               buf.append("Description: \n");
-               buf.append("<input type=\"text\" name=\"name\" 
value=\""+origDesc+"\" size=\"80\" />\n");
-               buf.append("<br />\n");
-               
-               if (mode == MODE_ADD) {
-                       buf.append("<input type=\"submit\" name=\"addbookmark\" 
value=\"Add bookmark\" class=\"confirm\" />\n");
-               } else {
-                       buf.append("<input type=\"submit\" 
name=\"update_"+b.hashCode()+"\" value=\"Update bookmark\" class=\"confirm\" 
/>\n");
-               }
-               
-               buf.append("<input type=\"submit\" value=\"Cancel\" 
class=\"cancel\" />\n");
-               buf.append("<input type=\"hidden\" name=\"managebookmarks\" 
value=\"yes\" />\n");
-               buf.append("</div>\n");
-               buf.append("</form>\n");
-               
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+               editForm.addChild("input", new String[] { "type", "value", 
"class" }, new String[] { "submit", "Cancel", "cancel" });
+               editForm.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "managebookmarks", "yes" });
+               return infobox;
        }

        public String supportedMethods() {

Modified: trunk/freenet/src/freenet/clients/http/filter/CSSReadFilter.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/CSSReadFilter.java    
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/filter/CSSReadFilter.java    
2006-08-09 19:01:12 UTC (rev 10005)
@@ -13,6 +13,7 @@
 import java.util.HashMap;

 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.io.Bucket;
 import freenet.support.io.BucketFactory;
@@ -43,9 +44,12 @@
                } catch (UnsupportedEncodingException e) {
                        os.close();
                        strm.close();
+                       HTMLNode explanation = new HTMLNode("p");
+                       explanation.addChild("b", "Unknown character set!");
+                       explanation.addChild("#", " The page you are about to 
display has an unknown character set. This means that we are not able to filter 
the page, and it may compromize your anonymity.");
                        throw new DataFilterException("Warning: Unknown 
character set ("+charset+")", "Warning: Unknown character set 
("+HTMLEncoder.encode(charset)+")",
                                        "<p><b>Unknown character set</b> The 
page you are about to display has an unknown character set. "+
-                                       "This means that we are not able to 
filter the page, and it may compromize your anonymity.");
+                                       "This means that we are not able to 
filter the page, and it may compromize your anonymity.", explanation);
                }
                CSSParser parser = new CSSParser(r, w, false, cb);
                parser.parse();

Modified: trunk/freenet/src/freenet/clients/http/filter/DataFilterException.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/DataFilterException.java      
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/filter/DataFilterException.java      
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.clients.http.filter;

+import freenet.support.HTMLNode;
+
 /**
  * Exception thrown when the data cannot be filtered.
  */
@@ -9,16 +11,22 @@
        final String rawTitle;
        final String encodedTitle;
        final String explanation;
+       final HTMLNode htmlExplanation;

-       DataFilterException(String raw, String encoded, String explanation) {
+       DataFilterException(String raw, String encoded, String explanation, 
HTMLNode htmlExplanation) {
                this.rawTitle = raw;
                this.encodedTitle = encoded;
                this.explanation = explanation;
+               this.htmlExplanation = htmlExplanation;
        }

        public String getExplanation() {
                return explanation;
        }
+       
+       public HTMLNode getHTMLExplanation() {
+               return htmlExplanation;
+       }

        public String getHTMLEncodedTitle() {
                return encodedTitle;

Modified: trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java       
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/clients/http/filter/HTMLFilter.java       
2006-08-09 19:01:12 UTC (rev 10005)
@@ -24,6 +24,7 @@

 import freenet.support.HTMLDecoder;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;
 import freenet.support.Logger;
 import freenet.support.io.Bucket;
 import freenet.support.io.BucketFactory;
@@ -48,9 +49,12 @@
                } catch (UnsupportedEncodingException e) {
                        os.close();
                        strm.close();
+                       HTMLNode explanation = new HTMLNode("p");
+                       explanation.addChild("b", "Unknown character set!");
+                       explanation.addChild("#", " The page you are about to 
display has an unknown character set. This means that we are not able to filter 
the page, and it may compromize your anonymity.");
                        throw new DataFilterException("Warning: Unknown 
character set ("+charset+")", "Warning: Unknown character set 
("+HTMLEncoder.encode(charset)+")",
                                        "<p><b>Unknown character set</b> The 
page you are about to display has an unknown character set. "+
-                                       "This means that we are not able to 
filter the page, and it may compromize your anonymity.");
+                                       "This means that we are not able to 
filter the page, and it may compromize your anonymity.", explanation);
                }
                HTMLParseContext pc = new HTMLParseContext(r, w, charset, cb);
                pc.run(temp);
@@ -451,7 +455,8 @@
        static void throwFilterException(String s) throws DataFilterException {
                // FIXME
                throw new DataFilterException(s, s,
-                               "The HTML filter failed to parse the page: "+s);
+                               "The HTML filter failed to parse the page: "+s,
+                               new HTMLNode("div", "The HTML filter failed to 
parse the page: " + s));
        }

        static class ParsedTag {

Modified: 
trunk/freenet/src/freenet/clients/http/filter/KnownUnsafeContentTypeException.java
===================================================================
--- 
trunk/freenet/src/freenet/clients/http/filter/KnownUnsafeContentTypeException.java
  2006-08-09 18:24:35 UTC (rev 10004)
+++ 
trunk/freenet/src/freenet/clients/http/filter/KnownUnsafeContentTypeException.java
  2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.clients.http.filter;

+import freenet.support.HTMLNode;
+
 public class KnownUnsafeContentTypeException extends 
UnsafeContentTypeException {
        private static final long serialVersionUID = -1;
        MIMEType type;
@@ -36,6 +38,36 @@

                return sb.toString();
        }
+       
+       public HTMLNode getHTMLExplanation() {
+               HTMLNode explanation = new HTMLNode("div");
+               explanation.addChild("p").addChild("b", type.readDescription);
+               explanation.addChild("p", "This is a potentially dangerous MIME 
type. If the node lets it through, your browser may " +
+                       "do bad things leading to compromize of your anonymity, 
and your IP address being exposed in "+
+                       "connection with this page. In particular:");
+               HTMLNode list = explanation.addChild("ul");
+               HTMLNode reason = list.addChild("li");
+               reason.addChild("span", "class", "warning", "Dangerous 
inlines:");
+               reason.addChild("#", " This type of content can contain inline 
images or " +
+                                       "videos, and can therefore load content 
from the non-anonymous open Web, exposing your " +
+                                       "IP address.");
+               reason = list.addChild("li");
+               reason.addChild("span", "class", "warning", "Dangerous links:");
+               reason.addChild("#", " This type of content can contain links 
to the " +
+                                       "non-anonymous Web; if you click on 
them (and they may be disguised), this may expose " +
+                                       "your IP address.");
+               reason = list.addChild("li");
+               reason.addChild("span", "class", "warning", "Dangerous 
scripting:");
+               reason.addChild("#", " This type of content can contain 
dangerous scripts "+
+                                       "which when executed may compromize 
your anonymity by connecting to the open Web or "+
+                                       "otherwise breach security.");
+               reason = list.addChild("li");
+               reason.addChild("span", "class", "warning", "Dangerous 
metadata:");
+               reason.addChild("#", " This type of content can contain 
metadata which may "+
+                                       "be displayed by some browsers or other 
software, which may contain dangerous links or inlines.");
+               explanation.addChild("p", "Since there is no built-in filter 
for this data, you should take the utmost of care!");
+               return explanation;
+       }

        public String getHTMLEncodedTitle() {
                return "Known dangerous type: "+type.primaryMimeType;

Modified: 
trunk/freenet/src/freenet/clients/http/filter/UnknownContentTypeException.java
===================================================================
--- 
trunk/freenet/src/freenet/clients/http/filter/UnknownContentTypeException.java  
    2006-08-09 18:24:35 UTC (rev 10004)
+++ 
trunk/freenet/src/freenet/clients/http/filter/UnknownContentTypeException.java  
    2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,6 +1,7 @@
 package freenet.clients.http.filter;

 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;

 public class UnknownContentTypeException extends UnsafeContentTypeException {
        private static final long serialVersionUID = -1;
@@ -34,4 +35,14 @@
                                "threat, for much the same reason, as can 
scripting, for this and other reasons.</p>";
        }

+       public HTMLNode getHTMLExplanation() {
+               return new HTMLNode("div", "Your Freenet node does not know 
anything about this MIME type. " +
+                               "This means that your browser might do 
something dangerous in response " +
+                               "to downloading this file. For example, many 
formats can contain embedded images " +
+                               "or videos, which are downloaded from the web; 
this is by no means innocuous, " +
+                               "because they can ruin your anonymity and 
expose your IP address (if the attacker " +
+                               "runs the web site or has access to its logs). 
Hyperlinks to the Web can also be a " +
+                               "threat, for much the same reason, as can 
scripting, for this and other reasons.");
+       }
+       
 }

Modified: 
trunk/freenet/src/freenet/clients/http/filter/UnsafeContentTypeException.java
===================================================================
--- 
trunk/freenet/src/freenet/clients/http/filter/UnsafeContentTypeException.java   
    2006-08-09 18:24:35 UTC (rev 10004)
+++ 
trunk/freenet/src/freenet/clients/http/filter/UnsafeContentTypeException.java   
    2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.clients.http.filter;

+import freenet.support.HTMLNode;
+
 /**
  * Thrown by the filter when it cannot guarantee the safety of the data, 
because it is an unknown type,
  * because it cannot be filtered, or because we do not know how to filter it.
@@ -14,6 +16,11 @@
        public abstract String getExplanation();

        /**
+        * Gets the contents of the error page as HTML.
+        */
+       public abstract HTMLNode getHTMLExplanation();
+       
+       /**
         * Get the title of the error page.
         */
        public abstract String getHTMLEncodedTitle();

Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java    2006-08-09 18:24:35 UTC (rev 
10004)
+++ trunk/freenet/src/freenet/node/Node.java    2006-08-09 19:01:12 UTC (rev 
10005)
@@ -112,6 +112,7 @@
 import freenet.support.Fields;
 import freenet.support.FileLoggerHook;
 import freenet.support.HexUtil;
+import freenet.support.HTMLNode;
 import freenet.support.IllegalBase64Exception;
 import freenet.support.ImmutableByteArrayWrapper;
 import freenet.support.LRUHashtable;
@@ -729,6 +730,7 @@
        // Things that's needed to keep track of
        public final PluginManager pluginManager;
        public freenet.plugin.PluginManager pluginManager2;
+       public freenet.plugin_new.PluginManager pluginManager3;

        // Client stuff that needs to be configged - FIXME
        static final int MAX_ARCHIVE_HANDLERS = 200; // don't take up much 
RAM... FIXME
@@ -1806,6 +1808,8 @@
                bookmarkManager = new BookmarkManager(this, fproxyConfig);
                pluginManager2 = new freenet.plugin.PluginManager(this);

+//             SubConfig pluginManagerConfig = new SubConfig("pluginmanager3", 
config);
+//             pluginManager3 = new 
freenet.plugin_new.PluginManager(pluginManagerConfig);

                // FProxy
                // FIXME this is a hack, the real way to do this is plugins
@@ -1891,6 +1895,10 @@
                                                return ERROR_SUN_NPTL;
                                        }

+                                       public HTMLNode getHTMLText() {
+                                               return new HTMLNode("div", 
ERROR_SUN_NPTL);
+                                       }
+
                                        public short getPriorityClass() {
                                                return UserAlert.CRITICAL_ERROR;
                                        }

Modified: trunk/freenet/src/freenet/node/useralerts/BuildOldAgeUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/BuildOldAgeUserAlert.java 
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/BuildOldAgeUserAlert.java 
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.node.useralerts;

+import freenet.support.HTMLNode;
+
 public class BuildOldAgeUserAlert implements UserAlert {
        private boolean isValid=true;
        public int lastGoodVersion = 0;
@@ -23,6 +25,10 @@
                return s;
        }

+       public HTMLNode getHTMLText() {
+               return new HTMLNode("div", "This node\u2019s software is older 
than the oldest version (Build #" + lastGoodVersion + ") allowed by the newest 
peers we try to connect to. Please update your node as soon as possible because 
you will not be able to connect to peers labelled \u201cTOO NEW\u201d until you 
do. (Freenet may leave your node in the dust of the past if you don\u2019t 
upgrade.");
+       }
+
        public short getPriorityClass() {
                return UserAlert.ERROR;
        }

Modified: trunk/freenet/src/freenet/node/useralerts/ExtOldAgeUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/ExtOldAgeUserAlert.java   
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/ExtOldAgeUserAlert.java   
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.node.useralerts;

+import freenet.support.HTMLNode;
+
 public class ExtOldAgeUserAlert implements UserAlert {
        private boolean isValid=true;

@@ -17,6 +19,10 @@
                return s;
        }

+       public HTMLNode getHTMLText() {
+               return new HTMLNode("div", "Your freenet-ext.jar file seems to 
be outdated: we strongly advise you to update it using 
http://downloads.freenetproject.org/alpha/freenet-ext.jar.";);
+       }
+
        public short getPriorityClass() {
                return UserAlert.ERROR;
        }

Modified: trunk/freenet/src/freenet/node/useralerts/IPUndetectedUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/IPUndetectedUserAlert.java        
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/IPUndetectedUserAlert.java        
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.node.useralerts;

+import freenet.support.HTMLNode;
+
 public class IPUndetectedUserAlert implements UserAlert {
        boolean isValid=true;

@@ -21,6 +23,14 @@
                        "your node with the 'Temporary IP address hint' <a 
href=\"/config/\">configuration parameter</a>.";
        }

+       public HTMLNode getHTMLText() {
+               HTMLNode textNode = new HTMLNode("div");
+               textNode.addChild("#", "Freenet was unable to determine your 
external IP address (or the IP address of your NAT-device or firewall). You can 
still exchange references with other people, however this will only work if the 
other user is not behind a NAT-device or firewall. As soon as you have 
connected to one other user in this way, Freenet will be able to determine your 
external IP address. You can determine your current IP address and tell your 
node with the \u201cTemporary IP Address Hint\u201d ");
+               textNode.addChild("a", "href", "/config/", "configuration 
parameter");
+               textNode.addChild("#", ".");
+               return textNode;
+       }
+
        public short getPriorityClass() {
                return UserAlert.ERROR;
        }

Modified: 
trunk/freenet/src/freenet/node/useralerts/MeaningfulNodeNameUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/MeaningfulNodeNameUserAlert.java  
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/MeaningfulNodeNameUserAlert.java  
2006-08-09 19:01:12 UTC (rev 10005)
@@ -4,6 +4,7 @@
 import freenet.config.SubConfig;
 import freenet.node.Node;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;

 public class MeaningfulNodeNameUserAlert implements UserAlert {
        private boolean isValid=true;
@@ -56,6 +57,26 @@
                return buf.toString();
        }

+       public HTMLNode getHTMLText() {
+               SubConfig sc = node.config.get("node");
+               Option o = sc.getOption("name");
+
+               HTMLNode alertNode = new HTMLNode("div");
+               HTMLNode textNode = alertNode.addChild("div", "It seems that 
your node\u2019s name is not defined. Setting up a node name does not affect 
your anonymity in any way but is useful for your peers to know who you are in 
case they have to reach you. You can change the node\u2019s name at the ");
+               textNode.addChild("a", "href", "/config/", "Configuration 
Page");
+               textNode.addChild("#", ". Putting your e-mail address or IRC 
nickname there is generally speaking a good idea and helps your friends to 
identify your node.");
+               HTMLNode formNode = alertNode.addChild("form", new String[] { 
"action", "method" }, new String[] { "/config/", "post" });
+               formNode.addChild("input", new String[] { "type", "name", 
"value" }, new String[] { "hidden", "formPassword", node.formPassword });
+               HTMLNode listNode = formNode.addChild("ul", "class", "config");
+               HTMLNode itemNode = listNode.addChild("li");
+               itemNode.addChild("span", "class", "configshortdesc", 
o.getShortDesc()).addChild("input", new String[] { "type", "name", "value" }, 
new String[] { "text", sc.getPrefix() + ".name", o.getValueString() });
+               itemNode.addChild("span", "class", "configlongdesc", 
o.getLongDesc());
+               formNode.addChild("input", new String[] { "type", "value" }, 
new String[] { "submit", "Apply" });
+               formNode.addChild("input", new String[] { "type", "value" }, 
new String[] { "reset", "Reset" });
+
+               return alertNode;
+       }
+
        public short getPriorityClass() {
                return UserAlert.WARNING;
        }

Modified: trunk/freenet/src/freenet/node/useralerts/N2NTMUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/N2NTMUserAlert.java       
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/N2NTMUserAlert.java       
2006-08-09 19:01:12 UTC (rev 10005)
@@ -2,6 +2,7 @@

 import freenet.node.PeerNode;
 import freenet.support.HTMLEncoder;
+import freenet.support.HTMLNode;

 // Node To Node Text Message User Alert
 public class N2NTMUserAlert implements UserAlert {
@@ -44,6 +45,17 @@
                return s;
        }

+       public HTMLNode getHTMLText() {
+               HTMLNode alertNode = new HTMLNode("div");
+               alertNode.addChild("p", "From: " + sourceNodename);
+               String[] lines = messageText.split("\n");
+               for (int i = 0, c = lines.length; i < c; i++) {
+                       alertNode.addChild("div", lines[i]);
+               }
+               alertNode.addChild("p").addChild("a", "href", 
"/send_n2ntm/?peernode_hashcode=" + sourcePeerNode.hashCode(), "Reply");
+               return alertNode;
+       }
+
        public short getPriorityClass() {
                return UserAlert.MINOR;
        }

Modified: trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java 
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/PeerManagerUserAlert.java 
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,6 +1,7 @@
 package freenet.node.useralerts;

 import freenet.node.Node;
+import freenet.support.HTMLNode;

 public class PeerManagerUserAlert implements UserAlert {

@@ -109,6 +110,45 @@
                return s;
        }

+       public HTMLNode getHTMLText() {
+               HTMLNode alertNode = new HTMLNode("div");
+
+               if (peers == 0) {
+                       alertNode.addChild("#", "This node has no peers to 
connect to, therefore it will not be able to function normally. Ideally you 
should connect to peers run by people you know (if you are paranoid, then 
people you trust; if not, then at least people you have talked to)");
+                       if (n.isTestnetEnabled()) {
+                               alertNode.addChild("#", ", but since this is a 
testnet node, we suggest that you log on to irc.freenode.net, channel 
#freenet-refs and ask around for somebody to connect to.");
+                       } else {
+                               alertNode.addChild("#", ". You could log on to 
irc.freenode.net, channel #freenet-refs and ask around for somebody to connect 
to, but remember that you are vulnerable to those you are directly connected 
to. (This is especially true in this early alpha of Freenet 0.7\u2026)");
+                               alertNode.addChild("br");
+                               alertNode.addChild("#", "BE SURE THAT THE OTHER 
PERSON HAS ADDED YOUR REFERENCE, TOO, AS ONE-WAY CONNECTION WILL NOT WORK!");
+                       }
+               } else if (conns == 0) {
+                       alertNode.addChild("#", "This node has not been able to 
connect to any other nodes so far; it will not be able to function normally. 
Hopefully some of your peers will connect soon; if not, try to get some more 
peers.");
+               } else if (conns == 1) {
+                       alertNode.addChild("#", "This node only has one 
connection. Performance will be impaired, and you have no anonymity nor even 
plausible deniability if that one person is malicious. Your node is attached to 
the network like a \u201cleaf\u201d and does not contribute to the 
network\u2019s health. Try to get at least 3 connected peers at any given 
time.");
+               } else if (conns == 2) {
+                       alertNode.addChild("#", "This node has only two 
connections. Performance and security will not be very good, and your node is 
not doing any routing for other nodes. Your node is embedded like a 
\u201cchain\u201d in the network and does not contribute to the network\u2019s 
health. Try to get at least 3 connected peers at any given time.");
+               } else if (neverConn > 
MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD) {
+                       alertNode.addChild("#", neverConn + " of your 
node\u2019s peers have never connected even once. You should not add peers 
unless you know that they have also added ");
+                       alertNode.addChild("a", "href", "/darknet/myref.txt", 
"your reference");
+                       alertNode.addChild("#", ".");
+               } else if ((peers - conns) > MAX_DISCONN_PEER_ALERT_THRESHOLD) {
+                       alertNode.addChild("#", (peers - conns) + " of your 
node\u2019s peers are disconnected. This will have a slight impact on your 
performance as disconnected peers also consume a small amount of bandwidth and 
CPU. Consider \u201ccleaning up\u201d your peer list. Note that ideally you 
should connect to nodes run by people you know.");
+               } else if (conns > MAX_CONN_ALERT_THRESHOLD) {
+                       alertNode.addChild("#", "Your node has too many 
connections (" + conns + " > " + MAX_CONN_ALERT_THRESHOLD + "). We do not 
encourage such a behaviour; Ubernodes are hurting the network.");
+               } else if (peers > MAX_PEER_ALERT_THRESHOLD) {
+                       alertNode.addChild("#", "Your node has too many peers 
(" + peers + " > " + MAX_PEER_ALERT_THRESHOLD + "). This will impact your 
performance as all peers (connected or not) consume bandwidth and CPU. Consider 
\u201ccleaning up\u201d your peer list.");
+               } else if (n.bwlimitDelayAlertRelevant && (bwlimitDelayTime > 
Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
+                       alertNode.addChild("#", "Your node has to wait too long 
for available bandwidth (" + bwlimitDelayTime + " > " + 
Node.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD + "). Increase your output 
bandwidth limit and/or remove some peers to improve the situation.");
+               } else if (n.nodeAveragePingAlertRelevant && 
(nodeAveragePingTime > Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
+                       alertNode.addChild("#", "Your node is having trouble 
talking with its peers quickly enough (" + nodeAveragePingTime + " > " + 
Node.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD + "). Decrease your output 
bandwidth limit and/or remove some peers to improve the situation.");
+               } else if (oldestNeverConnectedPeerAge > 
MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD) {
+                       alertNode.addChild("#", "One or more of your 
node\u2019s peers have never connected in the two weeks since they were added. 
Consider removing them since they are marginally affecting performance.");
+               } else throw new IllegalArgumentException("not valid");
+
+               return alertNode;
+       }
+
        public short getPriorityClass() {
                if((peers == 0) ||
                                (conns == 0) ||

Modified: 
trunk/freenet/src/freenet/node/useralerts/RevocationKeyFoundUserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/RevocationKeyFoundUserAlert.java  
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/RevocationKeyFoundUserAlert.java  
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.node.useralerts;

+import freenet.support.HTMLNode;
+
 public class RevocationKeyFoundUserAlert implements UserAlert {
        private final String msg;

@@ -25,6 +27,10 @@
                        "The revocation message is the following : "+msg;
        }

+       public HTMLNode getHTMLText() {
+               return new HTMLNode("div", "Your node has found the 
audo-updater\u2019s revocation key on the network. It means that our 
auto-updating system is likely to have been COMPROMIZED! Consequently, it has 
been disabled on your node to prevent \u201cbad things\u201d to be installed. 
We strongly advise you to check the project\u2019s website for updates. Please 
take care of verifying that the website hasn't been spoofed either. The 
revocation message is the following: " + msg);
+       }
+
        public short getPriorityClass() {
                return UserAlert.CRITICAL_ERROR;
        }

Modified: 
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
===================================================================
--- 
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java 
    2006-08-09 18:24:35 UTC (rev 10004)
+++ 
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java 
    2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,6 +1,7 @@
 package freenet.node.useralerts;

 import freenet.node.updater.NodeUpdater;
+import freenet.support.HTMLNode;

 public class UpdatedVersionAvailableUserAlert implements UserAlert {
        private boolean isValid, isReady;
@@ -43,6 +44,22 @@
                                "Your node is currently fetching the update and 
will ask you whether you want to update or not when it's ready.";
                }
        }
+
+       public HTMLNode getHTMLText() {
+               HTMLNode alertNode = new HTMLNode("div");
+               alertNode.addChild("#", "It seems that your node isn't running 
the latest version of the software.");
+               if (updater.inFinalCheck()) {
+                       alertNode.addChild("#", " Your node is currently doing 
a final check to verify the security of the update " + (version == readyVersion 
? "" : (" to " + readyVersion)) + ". (Finished checks: " + 
updater.getRevocationDNFCounter() + " of " + NodeUpdater.REVOCATION_DNF_MIN + 
")");
+               } else {
+                       if (isReady) {
+                               alertNode.addChild("#", " Updating to " + 
version + " is advised.");
+                               alertNode.addChild("form", new String[] { 
"action", "method" }, new String[] { "/", "post" }).addChild("input", new 
String[] { "type", "name", "value" }, new String[] { "submit", "update", 
"Update to " + readyVersion + " now" });
+                       } else {
+                               alertNode.addChild("#", " Your node is 
currently fetching the update and will ask you whether you want to update or 
not when it's ready.");
+                       }
+               }
+               return alertNode;
+       }

        public short getPriorityClass() {
                if(isReady || updater.inFinalCheck())

Modified: trunk/freenet/src/freenet/node/useralerts/UserAlert.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/UserAlert.java    2006-08-09 
18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/UserAlert.java    2006-08-09 
19:01:12 UTC (rev 10005)
@@ -1,5 +1,7 @@
 package freenet.node.useralerts;

+import freenet.support.HTMLNode;
+
 public interface UserAlert {

        /**
@@ -17,6 +19,11 @@
         * Content of alert (plain text).
         */
        public String getText();
+
+       /**
+        * Content of alert (HTML).
+        */
+       public HTMLNode getHTMLText();

        /**
         * Priority class

Modified: trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java
===================================================================
--- trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2006-08-09 18:24:35 UTC (rev 10004)
+++ trunk/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2006-08-09 19:01:12 UTC (rev 10005)
@@ -1,9 +1,13 @@
 package freenet.node.useralerts;

+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;

+import freenet.support.HTMLNode;
+
 /**
  * Collection of UserAlert's.
  */
@@ -41,46 +45,46 @@
                UserAlert a1 = (UserAlert) arg1;
                return a0.getPriorityClass() - a1.getPriorityClass();
        }
-
+       
        /**
         * Write the alerts as HTML to a StringBuffer
         */
-       public void toHtml(StringBuffer buf) {
+       public HTMLNode createAlerts() {
+               HTMLNode alertsNode = new HTMLNode("div");
                UserAlert[] alerts = getAlerts();
                for (int i = 0; i < alerts.length; i++) {
                        UserAlert alert = alerts[i];
                        if (!alert.isValid())
                                continue;

+                       HTMLNode alertNode = null;
                        short level = alert.getPriorityClass();
                        if (level <= UserAlert.CRITICAL_ERROR)
-                               buf.append("<div class=\"infobox 
infobox-error\">\n");
+                               alertNode = new HTMLNode("div", "class", 
"infobox infobox-error");
                        else if (level <= UserAlert.ERROR)
-                               buf.append("<div class=\"infobox 
infobox-alert\">\n");
+                               alertNode = new HTMLNode("div", "class", 
"infobox infobox-alert");
                        else if (level <= UserAlert.WARNING)
-                               buf.append("<div class=\"infobox 
infobox-warning\">\n");
+                               alertNode = new HTMLNode("div", "class", 
"infobox infobox-warning");
                        else if (level <= UserAlert.MINOR)
-                               buf.append("<div class=\"infobox 
infobox-information\">\n");
-                       //
-                       buf.append("<div class=\"infobox-header\">\n");
-                       buf.append(alert.getTitle());
-                       buf.append("</div>\n");
-                       //
-                       buf.append("<div class=\"infobox-content\">\n");
-                       buf.append(alert.getText());
-                       //
-                       if (alert.userCanDismiss())
-                               buf.append("<form method=\"post\" 
action=\".\"><input type=\"hidden\" name=\"disable\" 
value=\"").append(alert.hashCode()).append("\" /><input type=\"submit\" 
value=\"").append(alert.dismissButtonText()).append("\" /></form>");
-                       //
-                       buf.append("</div>\n");
-                       buf.append("</div>\n");
+                               alertNode = new HTMLNode("div", "class", 
"infobox infobox-information");
+
+                       alertsNode.addChild(alertNode);
+                       alertNode.addChild("div", "class", "infobox-header", 
alert.getTitle());
+                       HTMLNode alertContentNode = alertNode.addChild("div", 
"class", "infobox-content");
+                       alertContentNode.addChild(alert.getHTMLText());
+                       if (alert.userCanDismiss()) {
+                               HTMLNode dismissFormNode = 
alertContentNode.addChild("form", new String[] { "action", "method" }, new 
String[] { ".", "post" });
+                               dismissFormNode.addChild("input", new String[] 
{ "type", "name", "value" }, new String[] { "hidden", "disable", 
String.valueOf(alert.hashCode()) });
+                               dismissFormNode.addChild("input", new String[] 
{ "type", "value" }, new String[] { "submit", alert.dismissButtonText() });
+                       }
                }
+               return alertsNode;
        }

        /**
         * Write the alert summary as HTML to a StringBuffer
         */
-       public void toSummaryHtml(StringBuffer buf) {
+       public HTMLNode createSummary() {
                short highestLevel = 99;
                int numberOfCriticalError = 0;
                int numberOfError = 0;
@@ -96,17 +100,19 @@
                        if (level < highestLevel)
                                highestLevel = level;
                        if (level <= UserAlert.CRITICAL_ERROR)
-                               numberOfCriticalError += 1;
+                               numberOfCriticalError++;
                        else if (level <= UserAlert.ERROR)
-                               numberOfError += 1;
+                               numberOfError++;
                        else if (level <= UserAlert.WARNING)
-                               numberOfWarning += 1;
+                               numberOfWarning++;
                        else if (level <= UserAlert.MINOR)
-                               numberOfMinor += 1;
-                       totalNumber += 1;
+                               numberOfMinor++;
+                       totalNumber++;
                }
+               
                if (totalNumber == 0)
-                       return;
+                       return new HTMLNode("#", "");
+               
                boolean separatorNeeded = false;
                StringBuffer alertSummaryString = new StringBuffer(1024);
                if (numberOfCriticalError != 0) {
@@ -134,22 +140,23 @@
                if (separatorNeeded)
                        alertSummaryString.append(" | ");
                alertSummaryString.append("Total: ").append(totalNumber);
-               alertSummaryString.append(" | See them on <a href=\"/\">the 
Freenet FProxy Homepage</a>");
+
+               HTMLNode summaryBox = null;
+
                if (highestLevel <= UserAlert.CRITICAL_ERROR)
-                       buf.append("<div class=\"infobox infobox-error\">\n");
+                       summaryBox = new HTMLNode("div", "class", "infobox 
infobox-error");
                else if (highestLevel <= UserAlert.ERROR)
-                       buf.append("<div class=\"infobox infobox-alert\">\n");
+                       summaryBox = new HTMLNode("div", "class", "infobox 
infobox-alert");
                else if (highestLevel <= UserAlert.WARNING)
-                       buf.append("<div class=\"infobox infobox-warning\">\n");
+                       summaryBox = new HTMLNode("div", "class", "infobox 
infobox-warning");
                else if (highestLevel <= UserAlert.MINOR)
-                       buf.append("<div class=\"infobox 
infobox-information\">\n");
-               buf.append("<div class=\"infobox-header\">\n");
-               buf.append("Outstanding Alerts");
-               buf.append("</div>\n");
-               buf.append("<div class=\"infobox-content\">\n");
-               buf.append(alertSummaryString);
-               buf.append("</div>\n");
-               buf.append("</div>\n");
+                       summaryBox = new HTMLNode("div", "class", "infobox 
infobox-information");
+               summaryBox.addChild("div", "class", "infobox-header", 
"Outstanding alerts");
+               HTMLNode summaryContent = summaryBox.addChild("div", "class", 
"infobox-content", alertSummaryString.toString());
+               summaryContent.addChild("#", " | See them on ");
+               summaryContent.addChild("a", "href", "/", "the Freenet FProxy 
Homepage");
+               summaryContent.addChild("#", ".");
+               return summaryBox;
        }

 }

Modified: trunk/freenet/src/freenet/support/HTMLNode.java
===================================================================
--- trunk/freenet/src/freenet/support/HTMLNode.java     2006-08-09 18:24:35 UTC 
(rev 10004)
+++ trunk/freenet/src/freenet/support/HTMLNode.java     2006-08-09 19:01:12 UTC 
(rev 10005)
@@ -10,13 +10,13 @@

 public class HTMLNode {

-       private final String name;
+       protected final String name;

        private final String content;

-       private final Map attributes = new HashMap();
+       protected final Map attributes = new HashMap();

-       private final List children = new ArrayList();
+       protected final List children = new ArrayList();

        public HTMLNode(String name) {
                this(name, null);
@@ -91,9 +91,16 @@
        }

        public HTMLNode addChild(HTMLNode childNode) {
+               if (childNode == null) throw new NullPointerException();
                children.add(childNode);
                return childNode;
        }
+       
+       public void addChildren(HTMLNode[] childNodes) {
+               for (int i = 0, c = childNodes.length; i < c; i++) {
+                       addChild(childNodes[i]);
+               }
+       }

        public HTMLNode addChild(String nodeName) {
                return addChild(nodeName, null);
@@ -138,7 +145,11 @@
                        tagBuffer.append(" 
").append(HTMLEncoder.encode(attributeName)).append("=\"").append(HTMLEncoder.encode(attributeValue)).append("\"");
                }
                if (children.size() == 0) {
-                       tagBuffer.append(" />");
+                       if (name.equals("textarea") || name.equals("div")) {
+                               tagBuffer.append("></" + name + ">");
+                       } else {
+                               tagBuffer.append(" />");
+                       }
                } else {
                        tagBuffer.append(">");
                        for (int childIndex = 0, childCount = children.size(); 
childIndex < childCount; childIndex++) {
@@ -150,4 +161,34 @@
                return tagBuffer;
        }

-}
\ No newline at end of file
+       /**
+        * Special HTML node for the DOCTYPE declaration. This node differs 
from a
+        * normal HTML node in that it's child (and it should only have exactly 
one
+        * child, the "html" node) is rendered <em>after</em> this node.
+        * 
+        * @author David 'Bombe' Roden &lt;bombe at freenetproject.org&gt;
+        * @version $Id$
+        */
+       public static class HTMLDoctype extends HTMLNode {
+
+               protected final String systemUri;
+
+               /**
+                * 
+                */
+               public HTMLDoctype(String doctype, String systemUri) {
+                       super(doctype);
+                       this.systemUri = systemUri;
+               }
+
+               /**
+                * @see 
freenet.support.HTMLNode#generate(java.lang.StringBuffer)
+                */
+               public StringBuffer generate(StringBuffer tagBuffer) {
+                       tagBuffer.append("<!DOCTYPE ").append(name).append(" 
PUBLIC \"").append(systemUri).append("\">\n");
+                       return ((HTMLNode) children.get(0)).generate(tagBuffer);
+               }
+
+       }
+
+}


Reply via email to