Parse relation snippets

Change-Id: I41d7c091a95f7478126cfd56bbd9dba179797c9a
diff --git a/dev/demo/relations.html b/dev/demo/relations.html
index 89abf06..fc4ce98 100644
--- a/dev/demo/relations.html
+++ b/dev/demo/relations.html
@@ -15,12 +15,12 @@
           <div class="matchinfo">
             <div class="matchtree">
               <div id="treeRel"></div>
+              <div id="treeRel2"></div>
               <div id="treeHier"></div>
             </div>
           </div>
         </li>
       </ol>
     </div>
-
   </body>
 </html>
diff --git a/dev/demo/relationsdemo.js b/dev/demo/relationsdemo.js
index bba73ac..200894e 100644
--- a/dev/demo/relationsdemo.js
+++ b/dev/demo/relationsdemo.js
@@ -165,6 +165,11 @@
   // Todo: Probably rename to rel.draw()
   rel.show();
 
+  var rel2 = relClass.create(relSnippet);
+  document.getElementById("treeRel2").appendChild(rel2.element());
+  rel2.show();
+
+  
   var tree = treeClass.create(treeSnippet);
   document.getElementById("treeHier").appendChild(tree.element());
 });
diff --git a/dev/js/spec/matchSpec.js b/dev/js/spec/matchSpec.js
index edebff3..68bd981 100644
--- a/dev/js/spec/matchSpec.js
+++ b/dev/js/spec/matchSpec.js
@@ -176,18 +176,18 @@
     else
       cb({ "snippet": treeSnippet });
   };
-
+  
   describe('KorAP.InfoLayer', function () {
     
     var infoClass = require('match/infolayer');
 
     it('should be initializable', function () {
       expect(
-	function() { infoClass.create() }
+        function() { infoClass.create() }
       ).toThrow(new Error("Missing parameters"));
 
       expect(
-	function() { infoClass.create("base") }
+        function() { infoClass.create("base") }
       ).toThrow(new Error("Missing parameters"));
 
       var layer = infoClass.create("base", "s");
@@ -219,7 +219,7 @@
 
     it('should be initializable by Object', function () {
       expect(function() {
-	      matchClass.create()
+        matchClass.create()
       }).toThrow(new Error('Missing parameters'));
 
       expect(matchClass.create(match)).toBeTruthy();
@@ -342,23 +342,26 @@
       expect(info).toBeTruthy();
 
       info.getTable([], function (tablen) {
-	      table1 = tablen;
-	      done();
+        table1 = tablen;
+        done();
       });
     });
 
+
     it('should\'nt be parsable (async)', function () {
       expect(table1).not.toBeTruthy();
     });
 
+
     it('should load a working table async', function(done) {
       expect(info).toBeTruthy();
       info.getTable(undefined, function (tablem) {
-	      table2 = tablem;
-	      done();
+        table2 = tablem;
+        done();
       });
     });
     
+
     it('should parse into a table (async)', function () {
       expect(table2).toBeTruthy();
 
@@ -375,10 +378,9 @@
 
       expect(table2.getValue(2, "cnx", "l")[0]).toBe("fähig");
       expect(table2.getValue(2, "cnx", "l")[1]).toBe("leistung");
-
-      
     });
 
+    
     it('should parse into a table view', function () {
       var matchElement = matchElementFactory();
       expect(matchElement.tagName).toEqual('LI');
@@ -423,21 +425,24 @@
       expect(infotable.children[1].classList.contains('addtree')).toBeTruthy();
     });
 
+
     var tree;
     it('should parse into a tree (async) 1', function (done) {
       var info = matchClass.create(match).info();
       expect(info).toBeTruthy();
       info.getTree(undefined, undefined, function (treem) {
-	      tree = treem;
-	      done();
+        tree = treem;
+        done();
       });
     });
 
+
     it('should parse into a tree (async) 2', function () {
       expect(tree).toBeTruthy();
       expect(tree.nodes()).toEqual(49);
     });
 
+
     var matchElement, info;
     // var info, matchElement;
     it('should parse into a tree view', function () {      
@@ -467,13 +472,15 @@
       expect(infotable.children[1].classList.contains('addtree')).toBeTruthy();
     });
 
+
     it('should add a tree view async 1', function (done) {
       expect(info).toBeTruthy();
       info.addTree('mate', 'beebop', function () {
-	      done();
+        done();
       });
     });
 
+
     it('should add a tree view async 2', function () {
       // With added tree
       var infotable = matchElement.children[2];
@@ -505,8 +512,8 @@
       var info = matchClass.create(match).info();
       expect(info).toBeTruthy();
       info.getTable(undefined, function (x) {
-	table = x;
-	done();
+        table = x;
+        done();
       });
     });
 
@@ -559,8 +566,8 @@
       var info = matchClass.create(match).info();
       expect(info).toBeTruthy();
       info.getTree(undefined, undefined, function (y) {
-	tree = y;
-	done();
+        tree = y;
+        done();
       });
     });
 
@@ -584,14 +591,51 @@
   });
 
 
+  describe('KorAP.MatchRelation', function () {
+    var relClass = require('match/relations')
+
+    var relExample = "<span class=\"context-left\"></span>" +
+        "<span class=\"match\">" +
+        "  <span xml:id=\"token-GOE/AGA/01784-p199\">" +
+        "    <span xlink:title=\"malt/d:ADV\" " +
+        "          xlink:type=\"simple\" " +
+        "          xlink:href=\"#token-GOE/AGA/01784-p199\">dann</span>" +
+        "  </span>" +
+        " zog " +
+        "  <span xlink:title=\"malt/d:SUBJ\" " +
+        "        xlink:type=\"simple\" " +
+        "        xlink:href=\"#token-GOE/AGA/01784-p199\">ich</span>" +
+        "  <span xml:id=\"token-GOE/AGA/01784-p202\">" +
+        "    <span xlink:title=\"malt/d:OBJA\" " +
+        "          xlink:type=\"simple\" " +
+        "          xlink:href=\"#token-GOE/AGA/01784-p199\">mich</span>" +
+        "  </span>" +
+        "</span>" +
+        "<span class=\"context-right\"></span>";
+
+
+    it('should be initializable', function () {
+      var tree = relClass.create();
+      expect(tree.size()).toBe(0);
+    });
+
+    it('should be parse string data', function () {
+      var tree = relClass.create(relExample);
+      expect(tree.size()).toBe(4);
+    });
+
+
+  });
+
+  
   describe('KorAP.MatchTreeMenu', function () {
     var matchTreeMenu = require('match/treemenu');
     var matchTreeItem = require('match/treeitem');
 
     it('should be initializable', function () {
       var menu = matchTreeMenu.create(undefined, [
-	      ['cnx/c', 'cnx', 'c'],
-	      ['xip/c', 'xip', 'c']
+        ['cnx/c', 'cnx', 'c'],
+        ['xip/c', 'xip', 'c']
       ]);
 
       expect(menu.itemClass()).toEqual(matchTreeItem);
@@ -609,5 +653,4 @@
   // table.element();
   // tree = view.toTree();
   // tree.element();
-
 });
diff --git a/dev/js/src/match/info.js b/dev/js/src/match/info.js
index c2154a5..249cad5 100644
--- a/dev/js/src/match/info.js
+++ b/dev/js/src/match/info.js
@@ -7,12 +7,14 @@
   'match/tree',
   'match/treemenu',
   'match/querycreator',
+  'match/relations',
   'util'
 ], function (infoLayerClass,
 	           matchTableClass,
 	           matchTreeClass,
 	           matchTreeMenuClass,
-             matchQueryCreator) {
+             matchQueryCreator,
+             matchRelClass) {
   
   // Override 
   KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
diff --git a/dev/js/src/match/relations.js b/dev/js/src/match/relations.js
index dfe2241..137eb63 100644
--- a/dev/js/src/match/relations.js
+++ b/dev/js/src/match/relations.js
@@ -1,36 +1,143 @@
-define(function () {
+define([], function () {
   "use strict";
 
   var svgNS = "http://www.w3.org/2000/svg";
+  var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
 
   return {
     create : function (snippet) {
-      var obj = Object.create(this)._init(snippet);
+      var obj = Object.create(this);
       obj._tokens = [];
       obj._arcs = []
       obj._tokenElements = [];
       obj._y = 0;
 
       // Some configurations
-      obj.maxArc = 200; // maximum height of the bezier control point
+      obj.maxArc      = 200; // maximum height of the bezier control point
       obj.overlapDiff = 40;
-      obj.arcDiff = 15;
-      obj.anchorDiff = 8;
+      obj.arcDiff     = 15;
+      obj.anchorDiff  = 8;
       obj.anchorStart = 15;
-      obj.tokenSep = 30;
-      obj.xPadding = 10;
-      obj.yPadding = 5;
-      return obj;
+      obj.tokenSep    = 30;
+      obj.xPadding    = 10;
+      obj.yPadding    = 5;
+      return obj._init(snippet);
     },
     
     _init : function (snippet) {
-      /*
-      var html = document.createElement("div");
-      html.innerHTML = snippet;
-      */
+
+      if (snippet != undefined && snippet != null) {
+        var html = document.createElement("div");
+        html.innerHTML = snippet;
+
+        // Establish temporary parsing memory
+        this.temp = {
+
+          // Remember the map id => pos
+          target : {},
+
+          // Remember edge definitions
+          edges : [],
+
+          // Keep track of the current token position
+          pos : 0
+        };
+        this._parse(0, html.childNodes, undefined);
+
+        // Establish edge list
+        var targetMap = this.temp['target'];
+        var edges = this.temp['edges'];
+        for (var i in edges) {
+          var edge = edges[i];
+
+          // Check the target identifier
+          var targetID = edge.targetID;
+          var target = targetMap[targetID];
+
+          if (target != undefined) {
+
+            // Add relation
+            this.addRel({
+              start : edge.src,
+              end : target,
+              direction : 'uni',
+              label : edge.label
+            });
+          };
+        };
+
+        // Reset parsing memory
+        this.temp = {};
+      };
+
       return this;
     },
 
+    _parse : function (parent, children, mark) {
+      for (var i in children) {
+        var c = children[i];
+
+        // Element node
+        if (c.nodeType == 1) {
+
+          // Node is an identifier
+          if (c.hasAttribute('xml:id')) {
+
+            // Remember that pos has this identifier
+            this.temp['target'][c.getAttribute('xml:id')] = this.temp['pos'];
+          }
+
+          // Node is a relation
+          else if (c.hasAttribute('xlink:href')) {
+            var label, target;
+
+            target = c.getAttribute('xlink:href');
+            target = target.replace(/^#/, "");
+
+            if (c.hasAttribute('xlink:title')) {
+              label = this._clean(c.getAttribute('xlink:title'));
+            };
+
+            // Remember the defined edge
+            this.temp['edges'].push({
+              label : label,
+              src : this.temp['pos'],
+              targetID : target
+            });
+          };
+
+          if (c.hasChildNodes()) {
+            this._parse(0, c.childNodes, mark);
+          };
+        }
+
+        // Text node
+        else if (c.nodeType == 3) {
+
+          // Check, if there is a non-whitespace token
+          if (c.nodeValue !== undefined) {
+            var str = c.nodeValue.trim();
+            if (str !== undefined && str.length > 0) {
+
+              // Add token to token list
+              this.addToken(str);
+
+              // Move token position
+              this.temp['pos']++;
+            };
+          };
+        }
+      };
+    },
+
+    // Remove foundry and layer for labels
+    _clean : function (title) {
+      return title.replace(_TermRE, "$3");
+    },
+
+    size : function () {
+      return this._tokens.length;
+    },
 
     // This is a shorthand for SVG element creation
     _c : function (tag) {
diff --git a/dev/js/src/match/treeitem.js b/dev/js/src/match/treeitem.js
index 9ddb1fe..c284de3 100644
--- a/dev/js/src/match/treeitem.js
+++ b/dev/js/src/match/treeitem.js
@@ -1,4 +1,6 @@
 define(['menu/item'], function (itemClass) {
+  "use strict";
+
   /**
    * Menu item for tree view choice.
    */