Added more group merging and toString
diff --git a/public/js/spec/vcSpec.js b/public/js/spec/vcSpec.js
index aa57b4f..dbb60b0 100644
--- a/public/js/spec/vcSpec.js
+++ b/public/js/spec/vcSpec.js
@@ -9,7 +9,10 @@
       for (var prop in overwrites) {
 	newObj[prop] = overwrites[prop];
       };
-      return objClass.create().fromJson(newObj);
+      if (objClass === KorAP.VirtualCollection)
+	return objClass.render(newObj);
+      else
+	return objClass.create().fromJson(newObj);
     }
   }
 };
@@ -172,7 +175,7 @@
     expect(doc).toBeUndefined();
   });
 
-  it('should be serializale', function () {
+  it('should be serializale to JSON', function () {
 
     // Empty doc
     var doc = KorAP.Doc.create();
@@ -229,6 +232,38 @@
       "key" : 'pubDate'
     }));
   });
+
+  it('should be serializale to String', function () {
+
+    // Empty doc
+    var doc = KorAP.Doc.create();
+    expect(doc.toString()).toEqual("");
+
+    // Serialize string
+    doc = stringFactory.create();
+    expect(doc.toString()).toEqual('author = "Max Birkendale"');
+
+    // Serialize string with quotes
+    doc = stringFactory.create({ "value" : 'Max "Der Coole" Birkendate'});
+    expect(doc.toString()).toEqual('author = "Max \\"Der Coole\\" Birkendate"');
+
+    // Serialize regex
+    doc = regexFactory.create();
+    expect(doc.toString()).toEqual('title = /[^b]ee.+?/');
+
+    doc = regexFactory.create({
+      match: "match:ne"
+    });
+    expect(doc.toString()).toEqual('title != /[^b]ee.+?/');
+
+    doc = dateFactory.create();
+    expect(doc.toString()).toEqual('pubDate in 2014-11-05');
+
+    doc = dateFactory.create({
+      value : "2014"
+    });
+    expect(doc.toString()).toEqual('pubDate in 2014');
+  });
 });
 
 
@@ -302,7 +337,7 @@
     expect(op2.value()).toEqual("2014-12-05");
     expect(op2.matchop()).toEqual("eq");
 
-    // Create empty group
+    // Append empty group
     var newGroup = docGroup.append(KorAP.DocGroup.create());
     newGroup.operation('or');
     newGroup.append(docFactory.create());
@@ -349,7 +384,7 @@
     expect(op5.matchop()).toEqual("ne");
   });
 
-  it('should be serializable', function () {
+  it('should be serializable to JSON', function () {
     var docGroup = docGroupFactory.create();
 
     expect(docGroup.toJson()).toEqual(jasmine.objectContaining({
@@ -373,6 +408,55 @@
       ]
     }));
   });
+
+  it('should be serializable to String', function () {
+    var docGroup = docGroupFactory.create();
+    expect(docGroup.toString()).toEqual('author = "Max Birkendale" & pubDate in 2014-12-05');
+
+    docGroup = docGroupFactory.create({
+      "@type" : "korap:docGroup",
+      "operation" : "operation:or",
+      "operands" : [
+	{
+	  "@type": 'korap:doc',
+	  "key" : 'author',
+	  "match": 'match:eq',
+	  "value": 'Max Birkendale',
+	  "type": 'type:string'
+	},
+	{
+	  "@type" : "korap:docGroup",
+	  "operation" : "operation:and",
+	  "operands" : [
+	    {
+	      "@type": 'korap:doc',
+	      "key": 'pubDate',
+	      "match": 'match:geq',
+	      "value": '2014-05-12',
+	      "type": 'type:date'
+	    },
+	    {
+	      "@type": 'korap:doc',
+	      "key": 'pubDate',
+	      "match": 'match:leq',
+	      "value": '2014-12-05',
+	      "type": 'type:date'
+	    },
+	    {
+	      "@type": 'korap:doc',
+	      "key": 'foo',
+	      "match": 'match:ne',
+	      "value": '[a]?bar',
+	      "type": 'type:regex'
+	    }
+	  ]
+	}
+      ]
+    });
+    expect(docGroup.toString()).toEqual(
+      'author = "Max Birkendale" | (pubDate since 2014-05-12 & pubDate until 2014-12-05 & foo != /[a]?bar/)'
+    );
+  });
 });
 
 describe('KorAP.UnspecifiedDoc', function () {
@@ -399,11 +483,12 @@
       "type": 'type:date'      
     });
 
+    // Add unspecified object
+    docGroup.append();
+
     expect(docGroup.element().getAttribute('class')).toEqual('docGroup');
     expect(docGroup.element().children[0].getAttribute('class')).toEqual('doc');
 
-    // Add unspecified object
-    docGroup.append();
     var unspec = docGroup.element().children[1];
     expect(unspec.getAttribute('class')).toEqual('unspecified');
 
@@ -558,6 +643,90 @@
 });
 
 describe('KorAP.VirtualCollection', function () {
+
+  var simpleGroupFactory = buildFactory(KorAP.DocGroup, {
+    "@type" : "korap:docGroup",
+    "operation" : "operation:and",
+    "operands" : [
+      {
+	"@type": 'korap:doc',
+	"key" : 'author',
+	"match": 'match:eq',
+	"value": 'Max Birkendale',
+	"type": 'type:string'
+      },
+      {
+	"@type": 'korap:doc',
+	"key": 'pubDate',
+	"match": 'match:eq',
+	"value": '2014-12-05',
+	"type": 'type:date'
+      }
+    ]
+  });
+
+  var nestedGroupFactory = buildFactory(KorAP.VirtualCollection, {
+    "@type" : "korap:docGroup",
+    "operation" : "operation:or",
+    "operands" : [
+      {
+	"@type": 'korap:doc',
+	"key" : 'author',
+	"match": 'match:eq',
+	"value": 'Max Birkendale',
+	"type": 'type:string'
+      },
+      {
+	"@type" : "korap:docGroup",
+	"operation" : "operation:and",
+	"operands" : [
+	  {
+	    "@type": 'korap:doc',
+	    "key": 'pubDate',
+	    "match": 'match:geq',
+	    "value": '2014-05-12',
+	    "type": 'type:date'
+	  },
+	  {
+	    "@type": 'korap:doc',
+	    "key": 'pubDate',
+	    "match": 'match:leq',
+	    "value": '2014-12-05',
+	    "type": 'type:date'
+	  }
+	]
+      }
+    ]
+  });
+
+  var flatGroupFactory = buildFactory(KorAP.VirtualCollection, {
+    "@type" : "korap:docGroup",
+    "operation" : "operation:and",
+    "operands" : [
+      {
+	"@type": 'korap:doc',
+	"key": 'pubDate',
+	"match": 'match:geq',
+	"value": '2014-05-12',
+	"type": 'type:date'
+      },
+      {
+	"@type": 'korap:doc',
+	"key": 'pubDate',
+	"match": 'match:leq',
+	"value": '2014-12-05',
+	"type": 'type:date'
+      },
+      {
+	"@type": 'korap:doc',
+	"key": 'foo',
+	"match": 'match:eq',
+	"value": 'bar',
+	"type": 'type:string'
+      }
+    ]
+  });
+  
   it('should be initializable', function () {
     var vc = KorAP.VirtualCollection.render();
     expect(vc.element().getAttribute('class')).toEqual('vc');
@@ -590,26 +759,7 @@
   });
 
   it('should be based on a docGroup', function () {
-    var vc = KorAP.VirtualCollection.render({
-      "@type" : "korap:docGroup",
-      "operation" : "operation:and",
-      "operands" : [
-	{
-	  "@type": 'korap:doc',
-	  "key" : 'author',
-	  "match": 'match:eq',
-	  "value": 'Max Birkendale',
-	  "type": 'type:string'
-	},
-	{
-	  "@type": 'korap:doc',
-	  "key": 'pubDate',
-	  "match": 'match:eq',
-	  "value": '2014-12-05',
-	  "type": 'type:date'
-	}
-      ]
-    });
+    var vc = KorAP.VirtualCollection.render(simpleGroupFactory.create().toJson());
 
     expect(vc.element().getAttribute('class')).toEqual('vc');
     expect(vc.root().element().getAttribute('class')).toEqual('docGroup');
@@ -630,39 +780,8 @@
 
 
   it('should be based on a nested docGroup', function () {
-    var vc = KorAP.VirtualCollection.render({
-      "@type" : "korap:docGroup",
-      "operation" : "operation:or",
-      "operands" : [
-	{
-	  "@type": 'korap:doc',
-	  "key" : 'author',
-	  "match": 'match:eq',
-	  "value": 'Max Birkendale',
-	  "type": 'type:string'
-	},
-	{
-	  "@type" : "korap:docGroup",
-	  "operation" : "operation:and",
-	  "operands" : [
-	    {
-	      "@type": 'korap:doc',
-	      "key": 'pubDate',
-	      "match": 'match:geq',
-	      "value": '2014-05-12',
-	      "type": 'type:date'
-	    },
-	    {
-	      "@type": 'korap:doc',
-	      "key": 'pubDate',
-	      "match": 'match:leq',
-	      "value": '2014-12-05',
-	      "type": 'type:date'
-	    }
-	  ]
-	}
-      ]
-    });
+    var vc = nestedGroupFactory.create();
+
     expect(vc.element().getAttribute('class')).toEqual('vc');
     expect(vc.element().firstChild.getAttribute('class')).toEqual('docGroup');
     expect(vc.element().firstChild.children[0].getAttribute('class')).toEqual('doc');
@@ -674,53 +793,141 @@
     expect(vc.element().firstChild.children[2].getAttribute('class')).toEqual('operators');
   });    
 
-  it('should be modifiable by deletion', function () {
-    var vc = KorAP.VirtualCollection.render({
-      "@type" : "korap:docGroup",
-      "operation" : "operation:and",
-      "operands" : [
-	{
-	  "@type": 'korap:doc',
-	  "key": 'pubDate',
-	  "match": 'match:geq',
-	  "value": '2014-05-12',
-	  "type": 'type:date'
-	},
-	{
-	  "@type": 'korap:doc',
-	  "key": 'pubDate',
-	  "match": 'match:leq',
-	  "value": '2014-12-05',
-	  "type": 'type:date'
-	},
-	{
-	  "@type": 'korap:doc',
-	  "key": 'foo',
-	  "match": 'match:eq',
-	  "value": 'bar',
-	  "type": 'type:string'
-	}
-      ]
-    });
-
+  it('should be modifiable by deletion in flat docGroups', function () {
+    var vc = flatGroupFactory.create();
     var docGroup = vc.root();
+
+    expect(docGroup.element().getAttribute('class')).toEqual('docGroup');
+
     var doc = docGroup.getOperand(1);
     expect(doc.key()).toEqual("pubDate");
+    expect(doc.value()).toEqual("2014-12-05");
 
     // Remove operand 1
-    expect(docGroup.delOperand(doc)).not.toBeUndefined();
+    expect(docGroup.delOperand(doc).update()).not.toBeUndefined();
     expect(doc._element).toEqual(undefined);
 
     doc = docGroup.getOperand(1);
     expect(doc.key()).toEqual("foo");
 
     // Remove operand 1
-    expect(docGroup.delOperand(doc)).not.toBeUndefined();
+    expect(docGroup.delOperand(doc).update()).not.toBeUndefined();
     expect(doc._element).toEqual(undefined);
 
-    // Only one operand - but there shouldn't be a group anymore!
+    // Only one operand left ...
     expect(docGroup.getOperand(1)).toBeUndefined();
+    // ... but there shouldn't be a group anymore at all!
+    expect(docGroup.getOperand(0)).toBeUndefined();
+    
+    var obj = vc.root();
+    expect(obj.ldType()).toEqual("doc");
+    expect(obj.key()).toEqual("pubDate");
+    expect(obj.value()).toEqual("2014-05-12");
+
+    expect(obj.element().getAttribute('class')).toEqual('doc');
   }); 
+
+
+  it('should be modifiable by deletion in nested docGroups (root case)', function () {
+    var vc = nestedGroupFactory.create();
+
+    var docGroup = vc.root();
+    expect(docGroup.ldType()).toEqual("docGroup");
+    expect(docGroup.operation()).toEqual("or");
+
+    var doc = docGroup.getOperand(0);
+    expect(doc.key()).toEqual("author");
+    expect(doc.value()).toEqual("Max Birkendale");
+
+    docGroup = docGroup.getOperand(1);
+    expect(docGroup.operation()).toEqual("and");
+
+    doc = docGroup.getOperand(0);
+    expect(doc.key()).toEqual("pubDate");
+    expect(doc.matchop()).toEqual("geq");
+    expect(doc.value()).toEqual("2014-05-12");
+    expect(doc.type()).toEqual("date");
+
+    doc = docGroup.getOperand(1);
+    expect(doc.key()).toEqual("pubDate");
+    expect(doc.matchop()).toEqual("leq");
+    expect(doc.value()).toEqual("2014-12-05");
+    expect(doc.type()).toEqual("date");
+
+    // Remove first operand so everything becomes root
+    expect(
+      vc.root().delOperand(
+	vc.root().getOperand(0)
+      ).update().ldType()
+    ).toEqual("docGroup");
+    expect(vc.root().ldType()).toEqual("docGroup");
+    expect(vc.root().operation()).toEqual("and");
+  });
+
+  it('should be modifiable by deletion in nested docGroups (resolve group case)', function () {
+    var vc = nestedGroupFactory.create();
+
+    // Get nested group
+    var firstGroup = vc.root().getOperand(1);
+    firstGroup.append(simpleGroupFactory.create({ "operation" : "operation:or" }));
+    
+    // Structur is now:
+    // or(doc, and(doc, doc, or(doc, doc)))
+
+    // Get nested or in and
+    var orGroup = vc.root().getOperand(1).getOperand(2);
+    expect(orGroup.ldType()).toEqual("docGroup");
+    expect(orGroup.operation()).toEqual("or");
+
+    // Remove 
+    // Structur is now:
+    // or(doc, and(doc, doc, doc)))
+    expect(orGroup.delOperand(orGroup.getOperand(0)).update().operation()).toEqual("and");
+    expect(vc.root().getOperand(1).operands().length).toEqual(3);
+  });
+
+  it('should be modifiable by deletion in nested docGroups (identical group case)', function () {
+    var vc = nestedGroupFactory.create();
+
+    // Get nested group
+    var firstGroup = vc.root().getOperand(1);
+    firstGroup.append(simpleGroupFactory.create({ "operation" : "operation:or" }));
+    
+    // Structur is now:
+    // or(doc, and(doc, doc, or(doc, doc)))
+    expect(vc.toString()).toEqual(
+      'author = "Max Birkendale" | (pubDate since 2014-05-12 & pubDate until 2014-12-05 & (author = "Max Birkendale" | pubDate in 2014-12-05))'
+    );
+
+    var andGroup = vc.root().getOperand(1);
+
+    // Get leading docs in and
+    var doc1 = andGroup.getOperand(0);
+    expect(doc1.ldType()).toEqual("doc");
+    expect(doc1.value()).toEqual("2014-05-12");
+    var doc2 = andGroup.getOperand(1);
+    expect(doc2.ldType()).toEqual("doc");
+    expect(doc2.value()).toEqual("2014-12-05");
+
+    // Remove 2
+    expect(andGroup.delOperand(doc2).update().operation()).toEqual("and");
+    // Structur is now:
+    // or(doc, and(doc, or(doc, doc)))
+
+    expect(vc.toString()).toEqual(
+      'author = "Max Birkendale" | (pubDate since 2014-05-12 & (author = "Max Birkendale" | pubDate in 2014-12-05))'
+    );
+
+
+    // Remove 1
+    expect(andGroup.delOperand(doc1).update().operation()).toEqual("or");
+    // Structur is now:
+    // or(doc, doc, doc)
+
+    expect(vc.toString()).toEqual(
+      'author = "Max Birkendale" | author = "Max Birkendale" | pubDate in 2014-12-05'
+    );
+  });
 });
 
 describe('KorAP.Operators', function () {
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index 31ddc85..a713b09 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -32,11 +32,13 @@
   KorAP._validDateMatchRE   = new RegExp("^[lg]?eq$");
   KorAP._validDateRE        = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
   KorAP._validGroupOpRE     = new RegExp("^(?:and|or)$");
+  KorAP._quote              = new RegExp("([\"\\\\])", 'g');
 
-  var loc = (KorAP.Locale = KorAP.Locale || {} );
-  loc.AND = loc.AND || 'and';
-  loc.OR  = loc.OR  || 'or';
-  loc.DEL = loc.DEL || '×';
+  var loc   = (KorAP.Locale = KorAP.Locale || {} );
+  loc.AND   = loc.AND   || 'and';
+  loc.OR    = loc.OR    || 'or';
+  loc.DEL   = loc.DEL   || '×';
+  loc.EMPTY = loc.EMPTY || '⋯'
 
   function _bool (bool) {
     return (bool === undefined || bool === false) ? false : true;
@@ -50,7 +52,9 @@
   };
 
   KorAP.VirtualCollection = {
-    _root : undefined,
+    ldType : function () {
+      return null;
+    },
     create : function () {
       return Object.create(KorAP.VirtualCollection);
     },
@@ -60,10 +64,10 @@
       if (json !== undefined) {
 	// Root object
 	if (json['@type'] == 'korap:doc') {
-	  obj._root = KorAP.Doc.create(undefined, json);
+	  obj._root = KorAP.Doc.create(obj, json);
 	}
 	else if (json['@type'] == 'korap:docGroup') {
-	  obj._root = KorAP.DocGroup.create(undefined, json);
+	  obj._root = KorAP.DocGroup.create(obj, json);
 	}
 	else {
 	  KorAP.log(813, "Collection type is not supported");
@@ -73,7 +77,7 @@
 
       else {
 	// Add unspecified object
-	obj._root = KorAP.UnspecifiedDoc.create();
+	obj._root = KorAP.UnspecifiedDoc.create(obj);
       };
 
       // Add root element to root node
@@ -83,7 +87,9 @@
 
       return obj;
     },
-    root : function () {
+    root : function (obj) {
+      if (arguments.length === 1)
+	this._root = obj;
       return this._root;
     },
     element : function () {
@@ -93,6 +99,12 @@
       this._element = document.createElement('div');
       this._element.setAttribute('class', 'vc');
       return this._element;
+    },
+    toJson : function () {
+      return this._root.toJson();
+    },
+    toString : function () {
+      return this._root.toString();
     }
   };
 
@@ -106,7 +118,7 @@
 
   KorAP._delete = function (e) {
     var obj = this.parentNode.refTo;
-    obj.parent().delOperand(obj);
+    obj.parent().delOperand(obj).update();
     // Todo: CLEAR ALL THE THINGS!
   };
 
@@ -220,7 +232,7 @@
       _removeChildren(this._element);
 
       var ellipsis = document.createElement('span');
-      ellipsis.appendChild(document.createTextNode('⋯'));
+      ellipsis.appendChild(document.createTextNode(loc.EMPTY));
       this._element.appendChild(ellipsis);
 
       // Set operators
@@ -228,7 +240,7 @@
 	false,
 	false,
 	// No delete object, if this is the root
-	this._parent !== undefined ? true : false
+	(this._parent !== undefined && this.parent().ldType() !== null) ? true : false
       );
 
       this._element.appendChild(
@@ -315,11 +327,11 @@
 
     // Wrap a new operation around the doc element
     wrap : function (op) {
+/*
       var group = KorAP.DocGroup.create(undefined);
       group.append(this);
       group.append(null);
       this.parent(group);
-/*
       var div = document.createElement('div');
       div.setAttribute('data-operation', op);
       var parent = this.element.parent;
@@ -462,6 +474,49 @@
 	"value" : this.value() || '',
 	"type"  : "type:" + this.type()
       };
+    },
+    toString : function () {
+      if (!this.matchop() || !this.key())
+	return "";
+
+      // Build doc string based on key
+      var string = this.key() + ' ';
+
+      // Add match operator
+      switch (this.matchop()) {
+      case "ne":
+	string += '!=';
+	break;
+      case "contains":
+	string += '~';
+	break;
+      case "geq":
+	string += 'since';
+	break;
+      case "leq":
+	string += 'until';
+	break;
+      default:
+	string += (this.type() == 'date') ? 'in' : '=';
+	break;
+      };
+
+      string += ' ';
+
+      // Add value
+      switch (this.type()) {
+      case "date":
+	return string + this.value();
+	break;
+      case "regex":
+	return string + '/' + this.value() + '/';
+	break;
+      case "string":
+	return string + '"' + this.value().replace(KorAP._quote, '\\$1') + '"';
+	break;
+      };
+
+      return "...";
     }
   };
 
@@ -487,7 +542,6 @@
 	// Be aware of cyclic structures!
 	operand = KorAP.UnspecifiedDoc.create(this);
 	this._operands.push(operand);
-	this.update();
 	return operand;
       };
 
@@ -502,7 +556,6 @@
 	  // Be aware of cyclic structures!
 	  operand.parent(this);
 	  this._operands.push(operand);
-	  this.update();
 	  return operand;
 	};
 
@@ -515,7 +568,6 @@
 	if (doc === undefined)
 	  return;
 	this._operands.push(doc);
-	this.update();
 	return doc;
 
       case "korap:docGroup":
@@ -524,7 +576,6 @@
 	if (docGroup === undefined)
 	  return;
 	this._operands.push(docGroup);
-	this.update();
 	return docGroup;
 
       default:
@@ -533,12 +584,25 @@
       };
     },
     update : function () {
-      if (this._operands.length == 1) {
-	if (this.parent() !== undefined) {
-	  return this.parent().replaceOperand(
+
+      // There is only one operand in group
+      if (this._operands.length === 1) {
+	var parent = this.parent();
+
+	// Parent is a group
+	if (parent.ldType() !== null) {
+	  return parent.replaceOperand(
 	    this,
 	    this.getOperand(0)
-	  );
+	  ).update();
+	}
+
+	// Parent is vc root
+	else {
+console.log("parent is vc");
+	  parent.root(this.getOperand(0));
+	  this.destroy();
+	  return parent.root();
 	};
       };
 
@@ -577,6 +641,7 @@
       this._element = document.createElement('div');
       this._element.setAttribute('class', 'docGroup');
 
+      // Update the object - including optimization
       this.update();
 
       return this._element;
@@ -601,12 +666,30 @@
     },
 
     // Replace operand
-    replaceOperand : function (group, obj) {
+    replaceOperand : function (oldOp, newOp) {
       for (var i in this._operands) {
-	if (this._operands[i] === group) {
-	  this._operands[i] = obj;
-	  group.destroy();
-	  return this.update();
+	if (this._operands[i] === oldOp) {
+
+	  // Just insert a doc
+	  if (newOp.ldType() === "doc") {
+	    console.log("Insert doc in group");
+	    this._operands[i] = newOp;
+	  }
+	  // Insert a group of a different operation
+	  // (i.e. "and" in "or"/"or" in "and")
+	  else if (newOp.operation() != oldOp.operation()) {
+	    console.log("Insert group in group - no flatten");
+	    this._operands[i] = newOp;
+	  }
+
+	  // Flatten the group
+	  else {
+	    console.log("Insert group in group - flatten");
+	    for (var op in newOp.operands().reverse())
+	      this._operands.splice(i, 1, newOp.getOperand(op))
+	  };
+	  oldOp.destroy();
+	  return this;
 	}
       };
       return false;
@@ -623,7 +706,7 @@
 
 	  // Todo: Update has to check
 	  // that this may mean the group is empty etc.
-	  return this.update();
+	  return this;
 	};
       };
 
@@ -675,6 +758,13 @@
 	"operation" : "operation:" + this.operation(),
 	"operands"  : opArray
       };
+    },
+    toString : function () {
+      return this._operands.
+	map(function (op) {
+	  return op.ldType() === 'docGroup' ? '(' + op.toString() + ')' : op.toString()
+	}).
+	join(this.operation() === 'or' ? ' | ' : ' & ')
     }
   };
 
@@ -723,6 +813,7 @@
       if (this._operands !== undefined) {
 	for (var i in this._operands)
 	  this.getOperand(i).destroy();
+	this._operands = [];
       };
     },