morphological view for testbed
diff --git a/public/translateTable.js b/public/translateTable.js
new file mode 100644
index 0000000..ec8b00a
--- /dev/null
+++ b/public/translateTable.js
@@ -0,0 +1,179 @@
+// Store Table object in global object
+
+var splitRegex = /^([^\/]+?)(?:\/(.+?))?:([^:]+?)$/;
+
+var textFoundry = "Foundry";
+var textLayer = "Layer";
+
+
+// SnippetTable constructor
+function SnippetTable (snippet) {
+  this.info = [];
+  this.foundry = {};
+  this.layer = {};
+  this.pos = 0;
+
+  this.load = function (children) {
+    for (var i in children) {
+      var c = children[i];
+
+      // element with title
+      if (c.nodeType === 1) {
+	if (c.getAttribute("title")) {
+	  if (splitRegex.exec(c.getAttribute("title"))) {
+
+	    // Fill position with info
+	    var foundry, layer;
+	    if (RegExp.$2) {
+	      foundry = RegExp.$1;
+	      layer = RegExp.$2;
+	    }
+	    else {
+	      foundry = "base";
+	      layer = RegExp.$1
+	    };
+
+	    // Create object on position unless it exists
+	    if (!this.info[this.pos])
+	      this.info[this.pos] = {};
+
+	    this.info[this.pos][foundry + "/" + layer] = RegExp.$3;
+
+	    // Set foundry
+	    if (!this.foundry[foundry])
+	      this.foundry[foundry] = {};
+	    this.foundry[foundry][layer] = 1;
+
+	    // Set layer
+	    if (!this.layer[layer])
+	      this.layer[layer] = {};
+	    this.layer[layer][foundry] = 1;
+	  };
+	};
+
+	// depth search
+	if (c.hasChildNodes())
+	  this.load(c.childNodes);
+      }
+
+      // Leaf node - store string on position and go to next string
+      else if (c.nodeType === 3)
+	if (c.nodeValue.match(/[-a-z0-9]/i))
+	  this.info[this.pos++]["-s"] = c.nodeValue;
+    };
+    return this;
+  };
+
+  this.toTable = function (base) {
+    var i, f, l;
+
+    // Create HTML based on info
+    var d = document;
+    var table = d.createElement('table');
+    var tr = d.createElement('tr');
+    table.appendChild(tr);
+    var th = d.createElement('th');
+    th.appendChild(document.createTextNode(base === "layer" ? textLayer : textFoundry));
+
+    // Add icon to switch sorting
+    var span = document.createElement("span");
+
+    // Add switch event
+    var that = this;
+    span.addEventListener("click", function (obj) {
+      var x = that.toTable(base === "layer" ? "foundry" : "layer");
+      table.parentNode.replaceChild(x, table);
+    }, false);
+
+    span.setAttribute("class", "switchSort");
+    var icon = document.createElement("i");
+    icon.setAttribute("class", "fa fa-arrows-h");
+    span.appendChild(icon);
+    th.appendChild(span);
+
+    tr.appendChild(th);
+    th = d.createElement('th');
+    th.appendChild(document.createTextNode(base === "layer" ? textFoundry : textLayer));
+    tr.appendChild(th);
+
+    // Header line with surface strings
+    for (i in this.info) {
+      th = d.createElement('th');
+      tr.appendChild(th);
+      th.appendChild(d.createTextNode(this.info[i]["-s"]));
+    };
+
+    // Sort keys
+    var baseArray = [];
+    if (base === "layer") {
+      for (i in this.layer) {
+	baseArray.push(i);
+      };
+    }
+    else {
+      for (i in this.foundry) {
+	baseArray.push(i);
+      };
+    };
+    baseArray.sort();
+
+    // Annotations
+    for (f in baseArray) {
+      f = baseArray[f];
+      var thBase = d.createElement('th');
+      thBase.appendChild(d.createTextNode(f));
+
+      var rowSpan = 0;
+
+      // Sort keys
+      var subArray = [];
+      if (base === "layer") {
+	for (i in this.layer[f]) {
+	  subArray.push(i);
+	};
+      }
+      else {
+	for (i in this.foundry[f]) {
+	  subArray.push(i);
+	};
+      };
+      subArray.sort();
+
+      for (l in subArray) {
+	l = subArray[l];
+	tr = d.createElement('tr');
+	table.appendChild(tr);
+
+	if (rowSpan === 0)
+	  tr.appendChild(thBase);
+
+	th = d.createElement('th');
+	tr.appendChild(th);
+	th.appendChild(d.createTextNode(l));
+
+	var infoString = base === "layer" ? l + '/' + f : f + '/' + l;
+
+	for (t in this.info) {
+	  var td = d.createElement('td');
+	  tr.appendChild(td);
+
+	  if (this.info[t][infoString] !== undefined)
+	    td.appendChild(d.createTextNode(this.info[t][infoString]));
+	};
+	rowSpan++;
+      };
+      thBase.setAttribute("rowspan", rowSpan);
+    };
+    // return HTML object
+    return table;
+  };
+
+  // Create wrapper element
+  var html = document.createElement("table");
+  html.innerHTML = snippet;
+
+  // Create table object and load data from HTML
+  this.load(html.childNodes);
+};
+
+