Attempt to add tree view
diff --git a/public/js/src/matchInfo.js b/public/js/src/matchInfo.js
index 45f24f5..540a605 100644
--- a/public/js/src/matchInfo.js
+++ b/public/js/src/matchInfo.js
@@ -1,5 +1,5 @@
 /**
- * Make annotations visible.
+ * Visualize annotations.
  *
  * @author Nils Diewald
  */
@@ -13,8 +13,13 @@
 (function (KorAP) {
   "use strict";
 
+  // Default log message
+  KorAP.log = KorAP.log || function (type, msg) {
+    console.log(type + ": " + msg);
+  };
+
   KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
-  KorAP._TermRE = new RegExp("^([^\/]+?)(?:\/([^:]+?))?:(.+?)$");
+  KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
   KorAP._matchTerms  = ["corpusID", "docID", "textID"];
 
   // API requests
@@ -116,9 +121,10 @@
 	return;
 
       // Get info (may be cached)
+      // TODO: Async
       var matchResponse = KorAP.API.getMatchInfo(
 	this._match,
-	{ 'spans' : true, 'layer' : focus }
+	{ 'spans' : false, 'layer' : focus }
       );
 
       // Get snippet from match info
@@ -128,13 +134,32 @@
       };
 
       return null;
-    }
+    },
 
-    /*
     // Parse snippet for table visualization
     getTree : function (foundry, layer) {
+      var focus = [];
+
+      // TODO: Async
+      var matchResponse = KorAP.API.getMatchInfo(
+	this._match, {
+	  'spans' : true,
+	  'foundry' : foundry,
+	  'layer' : layer
+	}
+      );
+
+      // TODO: Support and cache multiple trees
+
+      // Get snippet from match info
+      if (matchResponse["snippet"] !== undefined) {
+	this._tree = KorAP.MatchTree.create(matchResponse["snippet"]);
+	return this._tree;
+      };
+
+      return null;
+ 
     }
-    */
   };
 
   KorAP.Match = {
@@ -301,15 +326,21 @@
 
       delete this._info[this._pos];
     },
+
+
+    /**
+     * Get HTML table view of annotations.
+     */
     element : function () {
       // First the legend table
       var d = document;
       var table = d.createElement('table');
-      var tr = table.appendChild(d.createElement('thead'))
-	.appendChild(
-	  d.createElement('tr')
-	);
 
+      // Single row in head
+      var tr = table.appendChild(d.createElement('thead'))
+	.appendChild(d.createElement('tr'));
+
+      // Add cell to row
       var addCell = function (type, name) {
 	var c = this.appendChild(d.createElement(type))
 	if (name === undefined)
@@ -374,10 +405,136 @@
     }
   };
 
-  
-  /*
-    KorAP.InfoFoundryLayer = {};
-    KorAP.InfoTree = {};
-    KorAP.MatchTable = {};
-  */
+  /**
+   * Visualize span annotations as a tree.
+   */
+  // http://java-hackers.com/p/paralin/meteor-dagre-d3
+  KorAP.MatchTree = {
+
+    create : function (snippet) {
+      return Object.create(KorAP.MatchTree)._init(snippet);
+    },
+
+    nodes : function () {
+      return this._next;
+    },
+
+    _init : function (snippet) {
+      this._next = new Number(0);
+
+      // Create html for traversal
+      var html = document.createElement("div");
+      html.innerHTML = snippet;
+      this._graph = new dagreD3.Digraph();
+
+      // This is a new root
+      this._graph.addNode(
+	this._next++,
+	{ "nodeclass" : "root" }
+      );
+
+      // Parse nodes from root
+      this._parse(0, html.childNodes);
+
+      // Root node has only one child - remove
+      if (Object.keys(this._graph._outEdges[0]).length === 1)
+	  this._graph.delNode(0);
+
+      // Initialize d3 renderer for dagre
+      this._renderer = new dagreD3.Renderer();
+      /*
+      var oldDrawNodes = this._renderer.drawNodes();
+      this._renderer.drawNodes(
+	function (graph, root) {
+	  var svgNodes = oldDrawNodes(graph, root);
+	  svgNodes.each(
+	    function (u) {
+	      d3.select(this).classed(graph.node(u).nodeClass, true);
+	    }
+	  );
+	}
+      );
+*/
+      // Disable pan and zoom
+      this._renderer.zoom(false);
+
+      html = undefined;
+      return this;
+    },
+
+    // Remove foundry and layer for labels
+    _clean : function (title) {
+      return title.replace(KorAP._TermRE, RegExp.$1);
+    },
+
+    // Parse the snippet
+    _parse : function (parent, children) {
+      for (var i in children) {
+	var c = children[i];
+
+	// Element node
+	if (c.nodeType == 1) {
+
+	  // Get title from html
+	  if (c.getAttribute("title")) {
+	    var title = this._clean(c.getAttribute("title"));
+
+	    // Add child node
+	    var id = this._next++;
+
+	    this._graph.addNode(id, {
+	      "nodeclass" : "middle",
+	      "label" : title
+	    });
+	    this._graph.addEdge(null, parent, id);
+
+	    // Check for next level
+	    if (c.hasChildNodes())
+	      this._parse(id, c.childNodes);
+	  }
+
+	  // Step further
+	  else if (c.hasChildNodes())
+	    this._parse(parent, c.childNodes);
+	}
+
+	// Text node
+	else if (c.nodeType == 3)
+
+	  if (c.nodeValue.match(/[-a-z0-9]/i)) {
+
+	    // Add child node
+	    var id = this._next++;
+	    this._graph.addNode(id, {
+	      "nodeclass" : "leaf",
+	      "label" : c.nodeValue
+	    });
+
+	    this._graph.addEdge(null, parent, id);
+	  };
+      };
+      return this;
+    },
+
+    element : function () {
+      this._element = document.createElement('div');
+      var svg = document.createElement('svg');
+      this._element.appendChild(svg);
+      var svgGroup = svg.appendChild(document.createElement('svg:g'));
+
+      svgGroup = d3.select(svgGroup);
+
+      console.log(svgGroup);
+      var layout = this._renderer.run(this._graph, svgGroup);
+/*
+      var w = layout.graph().width;
+      var h = layout.graph().height;
+      this._element.setAttribute("width", w + 10);
+      this._element.setAttribute("height", h + 10);
+      svgGroup.attr("transform", "translate(5, 5)");
+*/
+      return this._element;
+    }
+  };
+
 }(this.KorAP));