- new date operators in collection queries ("in", "on", "since" instead "=", "<",...)
- disambiguation date/string in collection queries
- jsonify collection tests
diff --git a/src/main/antlr/CollectionQuery.g4 b/src/main/antlr/CollectionQuery.g4
index d3b25b4..f46bbbf 100644
--- a/src/main/antlr/CollectionQuery.g4
+++ b/src/main/antlr/CollectionQuery.g4
@@ -38,13 +38,18 @@
 COLON				: ':';
 DASH				: '-';
 TILDE				: '~';
+SINCE				: 'since';
+UNTIL				: 'until';
+IN					: 'in';
+ON					: 'on';
 WS 					: ( ' ' | '\t' | '\r' | '\n' )+ -> skip ;
 fragment NO_RE      : ~[ \t\/];
 fragment ALPHABET   : ~('\t' | ' ' | '/' | '*' | '?' | '+' | '{' | '}' | '[' | ']'
                     | '(' | ')' | '|' | '"' | ',' | ':' | '\'' | '\\' | '!' | '=' | '~' | '&' | '^' | '<' | '>' );
 fragment ALPHA		: [a-zA-Z];
-DIGIT				: [0-9];
 
+
+DIGIT		: [0-9];
 DATE
 : DIGIT DIGIT DIGIT DIGIT (DASH DIGIT DIGIT (DASH DIGIT DIGIT)?)?
 ;
@@ -52,7 +57,9 @@
 NL                  : [\r\n] -> skip;
 ws                  : WS+;
 
-WORD                : ALPHABET* ALPHA ALPHABET*;  // needs to have at least one alphabetical letter (non-numeric)
+WORD				: ALPHABET+;
+//WORD                : ALPHABET* ALPHA ALPHABET*;  // needs to have at least one alphabetical letter (non-numeric)
+
 
 /*
  * Regular expressions
@@ -82,16 +89,29 @@
 : DATE
 ;
 
+dateOp
+: SINCE
+| UNTIL
+| IN
+| ON
+;
+
 operator
 :	(NEG? EQ) | LT | GT | LEQ | GEQ | TILDE;
 
 expr
-: meta
+: constraint
+| dateconstraint
 | token
 ;
 
-meta
-: (value operator)? field operator value
+dateconstraint
+: field dateOp date
+//| date dateOp field dateOp date
+;
+
+constraint
+: field operator value
 ;
 
 token
@@ -139,8 +159,9 @@
 	
 value
 : WORD
+| DIGIT+
+| DATE
 | multiword
-| date
 | regex
 ;
 
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryTree.java
index 46d4b3f..82f3a80 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryTree.java
@@ -3,6 +3,7 @@
 import de.ids_mannheim.korap.query.serialize.util.CollectionQueryLexer;
 import de.ids_mannheim.korap.query.serialize.util.CollectionQueryParser;
 import de.ids_mannheim.korap.util.QueryException;
+
 import org.antlr.v4.runtime.*;
 import org.antlr.v4.runtime.tree.*;
 
@@ -16,18 +17,9 @@
 public class CollectionQueryTree extends Antlr4AbstractSyntaxTree {
 
     private Parser parser;
-    private boolean verbose;
+    private static boolean verbose;
     private List<ParseTree> visited = new ArrayList<ParseTree>();
 
-
-    public CollectionQueryTree() {
-        verbose = false;
-    }
-
-    public CollectionQueryTree(boolean verbose) {
-        this.verbose = verbose;
-    }
-
     /**
      * Keeps track of active object.
      */
@@ -42,6 +34,13 @@
     LinkedList<Integer> objectsToPop = new LinkedList<Integer>();
     Integer stackedObjects = 0;
     
+    public CollectionQueryTree() {
+	}
+    
+    public CollectionQueryTree(boolean verbose) {
+    	CollectionQueryTree.verbose = verbose;
+	}
+    
     public CollectionQueryTree(String query) throws QueryException {
 		process(query);
 	}
@@ -64,9 +63,7 @@
         String nodeCat = getNodeCat(node);
         openNodeCats.push(nodeCat);
 
-
         stackedObjects = 0;
-
         if (verbose) {
             System.err.println(" " + objectStack);
             System.out.println(openNodeCats);
@@ -88,59 +85,39 @@
             stackedObjects++;
         }
 
-//        if (nodeCat.equals("orGroup")) {
-//            LinkedHashMap<String, Object> exprGroup = makeDocGroup("or");
-//            putIntoSuperObject(exprGroup);
-//            objectStack.push(exprGroup);
-//            stackedObjects++;
-//        }
-
-        if (nodeCat.equals("meta")) {
+        if (nodeCat.equals("constraint")) {
             ParseTree fieldNode = getFirstChildWithCat(node, "field");
             String field = fieldNode.getChild(0).toStringTree(parser);
-            List<ParseTree> operatorNodes = getChildrenWithCat(node, "operator");
-            List<ParseTree> valueNodes = getChildrenWithCat(node, "value");
-
-            if (valueNodes.size() == 1) {
-                LinkedHashMap<String, Object> term = makeDoc();
-                term.put("key", field);
-                term.putAll(parseValue(valueNodes.get(0)));
-                String match = operatorNodes.get(0).getText();
-                term.put("match", "match:" + interpretMatch(match));
-                if (checkOperatorValueConformance(term) == 1) {
-                	requestMap.put("collection", new LinkedHashMap<String,Object>());
-                	return;
-                }
-                putIntoSuperObject(term);
-            } else { // (valueNodes.size()==2)
-                LinkedHashMap<String, Object> termGroup = makeDocGroup("and");
-                ArrayList<Object> termGroupOperands = (ArrayList<Object>) termGroup.get("operands");
-
-                LinkedHashMap<String, Object> term1 = makeDoc();
-                term1.put("key", field);
-                term1.putAll(parseValue(valueNodes.get(0)));
-                String match1 = operatorNodes.get(0).getText();
-                term1.put("match", "match:" + invertInequation(interpretMatch(match1)));
-                termGroupOperands.add(term1);
-                if (checkOperatorValueConformance(term1) == 1) {
-                	requestMap.put("collection", new LinkedHashMap<String,Object>());
-                	return;
-                }
-
-                LinkedHashMap<String, Object> term2 = makeDoc();
-                term2.put("key", field);
-                term2.putAll(parseValue(valueNodes.get(1)));
-                String match2 = operatorNodes.get(1).getText();
-                term2.put("match", "match:" + interpretMatch(match2));
-                termGroupOperands.add(term2);
-                if (checkOperatorValueConformance(term2) == 1) {
-                	requestMap.put("collection", new LinkedHashMap<String,Object>());
-                	return;
-                }
-
-                putIntoSuperObject(termGroup);
+            ParseTree operatorNode = getFirstChildWithCat(node, "operator");
+            ParseTree valueNode = getFirstChildWithCat(node, "value");
+            LinkedHashMap<String, Object> term = makeDoc();
+            term.put("key", field);
+            term.putAll(parseValue(valueNode));
+            String match = operatorNode.getText();
+            term.put("match", "match:" + interpretMatchOperator(match));
+            if (checkOperatorValueConformance(term) == false) {
+            	requestMap = new LinkedHashMap<String,Object>();
+            	return;
             }
+            putIntoSuperObject(term);
+        }
+        
+        if (nodeCat.equals("dateconstraint")) {
+            ParseTree fieldNode = getFirstChildWithCat(node, "field");
+            String field = fieldNode.getChild(0).toStringTree(parser);
+            ParseTree dateOpNode = getFirstChildWithCat(node, "dateOp");
+            ParseTree dateNode = getFirstChildWithCat(node, "date");
 
+            LinkedHashMap<String, Object> term = makeDoc();
+            term.put("key", field);
+            term.putAll(parseValue(dateNode));
+            String match = dateOpNode.getText();
+            term.put("match", "match:" + interpretMatchOperator(match));
+            if (checkOperatorValueConformance(term) == false) {
+            	requestMap = new LinkedHashMap<String,Object>();
+            	return;
+            }
+            putIntoSuperObject(term);
         }
         
         if (nodeCat.equals("token")) {
@@ -153,7 +130,6 @@
 			if (getNodeCat(node.getChild(0)).equals("key")) {
 				// no 'term' child, but direct key specification: process here
 				LinkedHashMap<String,Object> term = makeTerm();
-				
 				String key = node.getChild(0).getText();
 				if (getNodeCat(node.getChild(0).getChild(0)).equals("regex")) {
 					isRegex = true;
@@ -187,7 +163,7 @@
 			visited.add(node.getChild(0));
 			visited.add(node.getChild(2));
 		}
-        
+
         objectsToPop.push(stackedObjects);
 
 		/*
@@ -218,43 +194,48 @@
 
     }
 
-
-    private int checkOperatorValueConformance(LinkedHashMap<String, Object> term) {
+	/**
+	 * Checks whether the combination of operator and value is legal (inequation operators <,>,<=,>= may only be used with dates).
+	 */
+    private boolean checkOperatorValueConformance(LinkedHashMap<String, Object> term) {
 		String match = (String) term.get("match");
 		String type = (String) term.get("type");
 		if (type == null || type.equals("type:regex")) {
 			if (!(match.equals("match:eq") || match.equals("match:ne") || match.equals("match:contains"))) {
 				addError(302, "You used an inequation operator with a string value.");
-				System.err.println("You used an inequation operator with a string value.");
-				return 1;
+				return false;
 			}
 		}
-		return 0;
+		return true;
 	}
 
 	private LinkedHashMap<String, Object> parseValue(ParseTree valueNode) {
     	LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
+    	if (getNodeCat(valueNode).equals("date")) {
+    		map.put("type", "type:date");
+    		checkDateValidity(valueNode);
+    	}
     	if (getNodeCat(valueNode.getChild(0)).equals("regex")) {
     		String regex = valueNode.getChild(0).getChild(0).toStringTree(parser);
     		map.put("value", regex.substring(1, regex.length()-1));
     		map.put("type", "type:regex");
     	} else if (getNodeCat(valueNode.getChild(0)).equals("multiword")) {
-    		String mw = "";
+    		String mw = ""; // multiword
     		for (int i=1; i<valueNode.getChild(0).getChildCount()-1; i++) {
     			mw += valueNode.getChild(0).getChild(i).getText() + " ";
     		}
     		map.put("value", mw.substring(0, mw.length()-1));
-    	} else if (getNodeCat(valueNode.getChild(0)).equals("date")) {
-    		map.put("type", "type:date");
-    		String value = valueNode.getChild(0).getChild(0).toStringTree(parser);
-    		map.put("value", value);
     	} else {
     		map.put("value", valueNode.getChild(0).toStringTree(parser));
     	}
 		return map;
 	}
 
-	private String interpretMatch(String match) {
+	private void checkDateValidity(ParseTree valueNode) {
+		// TODO ensure month is <= 12, day is <= 31 etc.
+	}
+
+	private String interpretMatchOperator(String match) {
         String out = null;
         switch (match) {
             case "<":
@@ -278,11 +259,30 @@
             case "~":
                 out = "contains";
                 break;    
-               
+            case "!~":
+                out = "notcontains";
+                break;    
+            case "in":
+                out = "eq";
+                break;
+            case "on":
+                out = "eq";
+                break;
+            case "until":
+                out = "leq";
+                break;    
+            case "since":
+                out = "geq";
+                break;
+            default:
+            	out = match;
+            	addError(302, "Unknown operator '"+match+"'.");
+            	break;
         }
         return out;
     }
-
+	
+	@Deprecated
     private String invertInequation(String op) {
         String inv = null;
         switch (op) {
@@ -312,9 +312,8 @@
             ArrayList<Object> topObjectOperands = (ArrayList<Object>) objectStack.get(objStackPosition).get("operands");
             topObjectOperands.add(object);
         } else {
-            // I want the raw object, not a wrapped
-//            requestMap.put("filter", object);
-        	requestMap.put("collection", object);
+        	requestMap = object;
+//        	requestMap.put("collection", object);
         }
     }
 
@@ -449,11 +448,13 @@
         query = "(textClass=wissenschaft & textClass=politik) | textClass=ausland";
         query = "1990<year<2010 & genre=Sport";
         query = "1990<year<2010";
-        query = "pubDate<Sport";
-//    	filter.verbose = true;
+        query = "textClass=Sport & year=2014";
+        query = "textClass=0154";
+        CollectionQueryTree.verbose = true;
         CollectionQueryTree filter = null;
         try {
         	 filter = new CollectionQueryTree(query);
+        	 filter.verbose = true;
         } catch (QueryException e) {
             e.printStackTrace();
         }
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
index f0ae995..f124a00 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
@@ -27,45 +27,45 @@
             .getLogger(QuerySerializer.class);
 
 
-    /**
-     * @param args
-     * @throws QueryException
-     */
-    public static void main(String[] args) {
-        /*
-         * just for testing...
-		 */
-        QuerySerializer jg = new QuerySerializer();
-        int i = 0;
-        String[] queries;
-        if (args.length == 0) {
-            queries = new String[]{
-
-            };
-        } else
-            queries = new String[]{args[0]};
-
-        for (String q : queries) {
-            i++;
-            try {
-                System.out.println(q);
-                String ql = "cosmas2";
-                jg.run(q, ql, System.getProperty("user.home") + "/" + ql + "_" + i + ".jsonld");
-                System.out.println();
-            } catch (NullPointerException npe) {
-                npe.printStackTrace();
-                System.out.println("null\n");
-            } catch (JsonGenerationException e) {
-                e.printStackTrace();
-            } catch (JsonMappingException e) {
-                e.printStackTrace();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } catch (QueryException e) {
-                e.printStackTrace();
-            }
-        }
-    }
+//    /**
+//     * @param args
+//     * @throws QueryException
+//     */
+//    public static void main(String[] args) {
+//        /*
+//         * just for testing...
+//		 */
+//        QuerySerializer jg = new QuerySerializer();
+//        int i = 0;
+//        String[] queries;
+//        if (args.length == 0) {
+//            queries = new String[]{
+//
+//            };
+//        } else
+//            queries = new String[]{args[0]};
+//
+//        for (String q : queries) {
+//            i++;
+//            try {
+//                System.out.println(q);
+//                String ql = "cosmas2";
+//                jg.run(q, ql, System.getProperty("user.home") + "/" + ql + "_" + i + ".jsonld");
+//                System.out.println();
+//            } catch (NullPointerException npe) {
+//                npe.printStackTrace();
+//                System.out.println("null\n");
+//            } catch (JsonGenerationException e) {
+//                e.printStackTrace();
+//            } catch (JsonMappingException e) {
+//                e.printStackTrace();
+//            } catch (IOException e) {
+//                e.printStackTrace();
+//            } catch (QueryException e) {
+//                e.printStackTrace();
+//            }
+//        }
+//    }
 
     /**
      * Runs the QuerySerializer by initializing the relevant AbstractSyntaxTree implementation (depending on specified query language)
@@ -77,24 +77,24 @@
      * @throws IOException
      * @throws QueryException
      */
-    public void run(String query, String queryLanguage, String outFile)
-            throws IOException, QueryException {
-        if (queryLanguage.equals("poliqarp")) {
-            ast = new PoliqarpPlusTree(query);
-        } else if (queryLanguage.toLowerCase().equals("cosmas2")) {
-            ast = new CosmasTree(query);
-        } else if (queryLanguage.toLowerCase().equals("poliqarpplus")) {
-            ast = new PoliqarpPlusTree(query);
-        } else if (queryLanguage.toLowerCase().equals("cql")) {
-            ast = new CQLTree(query);
-        } else if (queryLanguage.toLowerCase().equals("annis")) {
-            ast = new AqlTree(query);
-        } else {
-            throw new QueryException(queryLanguage + " is not a supported query language!");
-        }
-        Map<String, Object> requestMap = ast.getRequestMap();
-//        mapper.writeValue(new File(outFile), requestMap);
-    }
+//    public void run(String query, String queryLanguage, String outFile)
+//            throws IOException, QueryException {
+//        if (queryLanguage.equals("poliqarp")) {
+//            ast = new PoliqarpPlusTree(query);
+//        } else if (queryLanguage.toLowerCase().equals("cosmas2")) {
+//            ast = new CosmasTree(query);
+//        } else if (queryLanguage.toLowerCase().equals("poliqarpplus")) {
+//            ast = new PoliqarpPlusTree(query);
+//        } else if (queryLanguage.toLowerCase().equals("cql")) {
+//            ast = new CQLTree(query);
+//        } else if (queryLanguage.toLowerCase().equals("annis")) {
+//            ast = new AqlTree(query);
+//        } else {
+//            throw new QueryException(queryLanguage + " is not a supported query language!");
+//        }
+//        Map<String, Object> requestMap = ast.getRequestMap();
+////        mapper.writeValue(new File(outFile), requestMap);
+//    }
 
     public QuerySerializer setQuery(String query, String ql, String version)
             throws QueryException {
@@ -147,7 +147,7 @@
             Map<String, Object> requestMap = ast.getRequestMap();
             Map meta = (Map) requestMap.get("meta");
             if (collection != null)
-                requestMap.put("collections", collection);
+                requestMap.put("collection", collection);
             if (this.meta != null) {
                 meta.putAll(this.meta);
                 requestMap.put("meta", meta);
diff --git a/src/test/java/CollectionQueryTreeTest.java b/src/test/java/CollectionQueryTreeTest.java
index ebe9cc7..acf09d7 100644
--- a/src/test/java/CollectionQueryTreeTest.java
+++ b/src/test/java/CollectionQueryTreeTest.java
@@ -1,301 +1,378 @@
 import static org.junit.Assert.*;
-import de.ids_mannheim.korap.query.serialize.CollectionQueryTree;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
 import de.ids_mannheim.korap.util.QueryException;
+
 import org.junit.Test;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 public class CollectionQueryTreeTest {
 
-	CollectionQueryTree cqt;
-	String map;
-	private String query;
-	private String expected;
+	String query = "foo";
+	String ql = "poliqarpplus";
+	String collection;
+	ArrayList<JsonNode> operands;
 
+	QuerySerializer qs = new QuerySerializer();
+	ObjectMapper mapper = new ObjectMapper();
+	JsonNode res;
+	
 	@Test
-	public void testSimple() throws QueryException {
-		query = "textClass=Sport";
-		//      String regex1 = "{@type=korap:filter, filter={@type=korap:doc, attribute=textClass, key=Sport, match=match:eq}}";
-		expected = "{@type=korap:doc, key=textClass, value=Sport, match=match:eq}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-
-		query = "textClass!=Sport";
-		//	      String regex1 = "{@type=korap:filter, filter={@type=korap:doc, attribute=textClass, key=Sport, match=match:eq}}";
-		expected = "{@type=korap:doc, key=textClass, value=Sport, match=match:ne}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testContext() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=politik";
+		String contextString = "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals(contextString, res.get("@context").asText());
 	}
 	
 	@Test
-	public void testContains() throws QueryException {
-		query = "title~Mannheim";
-		expected = 
-			"{@type=korap:doc, key=title, value=Mannheim, match=match:contains}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testSimple() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=politik";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/key").asText());
+		assertEquals("politik", 		res.at("/collection/value").asText());
+		assertEquals("match:eq", 		res.at("/collection/match").asText());
 		
-		query = "title~\"IDS Mannheim\"";
-		expected = 
-			"{@type=korap:doc, key=title, value=IDS Mannheim, match=match:contains}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		collection = "textClass!=politik";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/key").asText());
+		assertEquals("politik", 		res.at("/collection/value").asText());
+		assertEquals("match:ne", 		res.at("/collection/match").asText());
 	}
 	
 	@Test
-	public void testTwoConjuncts() throws QueryException {
-		query = "textClass=Sport & year=2014";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-					"{@type=korap:doc, key=year, type=type:date, value=2014, match=match:eq}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-	}
-
-    //todo year type is not yet serialized!
-	@Test
-	public void testThreeConjuncts() throws QueryException {
-		query = "textClass=Sport & year=2014 & corpusID=WPD";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=year, type=type:date, value=2014, match=match:eq}," +
-						"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}" +
-					"]}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-	}
-	
-
-	@Test
-	public void testTwoDisjuncts() throws QueryException {
-		query = "textClass=Sport | year=2014";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:or, operands=[" +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-					"{@type=korap:doc, key=year, type=type:date, value=2014, match=match:eq}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testContains() throws QueryException, JsonProcessingException, IOException {
+		collection = "title~Mannheim";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("title", 			res.at("/collection/key").asText());
+		assertEquals("Mannheim", 		res.at("/collection/value").asText());
+		assertEquals("match:contains",  res.at("/collection/match").asText());
+		
+		collection = "title~\"IDS Mannheim\"";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("title",	 		res.at("/collection/key").asText());
+		assertEquals("IDS Mannheim",	res.at("/collection/value").asText());
+		assertEquals("match:contains",	res.at("/collection/match").asText());
 	}
 	
 	@Test
-	public void testThreeDisjuncts() throws QueryException {
-		query = "textClass=Sport | year=2014 | corpusID=WPD";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:or, operands=[" +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-					"{@type=korap:docGroup, operation=operation:or, operands=[" +
-						"{@type=korap:doc, key=year, type=type:date, value=2014, match=match:eq}," +
-						"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}" +
-					"]}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-	}
-
-
-	@Test
-	public void testMixed() throws QueryException {
-		query = "(textClass=Sport | textClass=ausland) & corpusID=WPD";
-		expected = 
-			
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:docGroup, operation=operation:or, operands=[" +
-						"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-						"{@type=korap:doc, key=textClass, value=ausland, match=match:eq}" +
-					"]}," +
-					"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testTwoConjuncts() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=Sport & pubDate in 2014";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/match").asText());
 		
-		query = "(textClass=Sport & textClass=ausland) & corpusID=WPD";
-		expected = 
-			
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-						"{@type=korap:doc, key=textClass, value=ausland, match=match:eq}" +
-					"]}," +
-					"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "(textClass=Sport & textClass=ausland) | (corpusID=WPD & author=White)";
-		expected = 
-			
-				"{@type=korap:docGroup, operation=operation:or, operands=[" +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-						"{@type=korap:doc, key=textClass, value=ausland, match=match:eq}" +
-					"]}," +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}," +
-						"{@type=korap:doc, key=author, value=White, match=match:eq}" +
-					"]}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "(textClass=Sport & textClass=ausland) | (corpusID=WPD & author=White & year=2010)";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:or, operands=[" +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-						"{@type=korap:doc, key=textClass, value=ausland, match=match:eq}" +
-					"]}," +
-					"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:doc, key=corpusID, value=WPD, match=match:eq}," +
-						"{@type=korap:docGroup, operation=operation:and, operands=[" +
-							"{@type=korap:doc, key=author, value=White, match=match:eq}," +
-							"{@type=korap:doc, key=year, type=type:date, value=2010, match=match:eq}" +
-						"]}" +
-					"]}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		collection = "textClass=Sport & pubDate=2014";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/value").asText());
+		assertEquals(true,				res.at("/collection/operands/1/type").isMissingNode());
+		assertEquals("match:eq",		res.at("/collection/operands/1/match").asText());
 	}
 
 	@Test
-	public void testDate() throws QueryException {
-		// search for pubDate between 1990 and 2010!
-		query = "1990<pubDate<2010";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:doc, key=pubDate, type=type:date, value=1990, match=match:gt}," +
-					"{@type=korap:doc, key=pubDate, type=type:date, value=2010, match=match:lt}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "pubDate>=1990";
-		expected = 
-				"{@type=korap:doc, key=pubDate, type=type:date, value=1990, match=match:geq}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "pubDate>=1990-05";
-		expected = 
-				"{@type=korap:doc, key=pubDate, type=type:date, value=1990-05, match=match:geq}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "pubDate>=1990-05-01";
-		expected = 
-				"{@type=korap:doc, key=pubDate, type=type:date, value=1990-05-01, match=match:geq}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testThreeConjuncts() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=Sport & pubDate in 2014 & corpusId=WPD";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:docGroup", 	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/operands/0/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/operands/0/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("corpusId", 		res.at("/collection/operands/1/operands/1/key").asText());
+		assertEquals("WPD",				res.at("/collection/operands/1/operands/1/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/1/match").asText());
 	}
-
 	@Test
-	public void testRegex() throws QueryException {
-		query = "author=/Go.*he/";
-		expected = 
-				"{@type=korap:doc, key=author, value=Go.*he, type=type:regex, match=match:eq}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testTwoDisjuncts() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=Sport | pubDate in 2014";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/match").asText());
 	}
 	
 	@Test
-	public void testContentFilter() throws QueryException {
-		query = "[base=Schwalbe]";
-		expected = 
-				"{@type=korap:token, wrap={@type=korap:term, layer=lemma, key=Schwalbe, match=match:eq}}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "[cnx/base=Schwalbe]";
-		expected = 
-				"{@type=korap:token, wrap={@type=korap:term, foundry=cnx, layer=lemma, key=Schwalbe, match=match:eq}}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "[base!=Schwalbe]";
-		expected = 
-				"{@type=korap:token, wrap={@type=korap:term, layer=lemma, key=Schwalbe, match=match:ne}}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
-		
-		query = "[base=Schwalbe] & [orth=Foul]";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-						"{@type=korap:token, wrap={@type=korap:term, layer=lemma, key=Schwalbe, match=match:eq}}," +
-						"{@type=korap:token, wrap={@type=korap:term, layer=orth, key=Foul, match=match:eq}}" +
-					"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testThreeDisjuncts() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=Sport | pubDate in 2014 | corpusId=WPD";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:docGroup", 	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/operands/0/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/operands/0/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("corpusId", 		res.at("/collection/operands/1/operands/1/key").asText());
+		assertEquals("WPD",				res.at("/collection/operands/1/operands/1/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/1/match").asText());
 	}
 	
 	@Test
-	public void testContentMetaMixed() throws QueryException {
-		query = "textClass=Sport & [base=Schwalbe]";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}," +
-					"{@type=korap:token, wrap={@type=korap:term, layer=lemma, key=Schwalbe, match=match:eq}}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+	public void testMixed() throws QueryException, JsonProcessingException, IOException {
+		collection = "textClass=Sport | (pubDate in 2014 & corpusId=WPD)";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:docGroup", 	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/operands/0/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/operands/0/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("corpusId", 		res.at("/collection/operands/1/operands/1/key").asText());
+		assertEquals("WPD",				res.at("/collection/operands/1/operands/1/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/1/match").asText());
 		
-		query = "[base=Schwalbe] & textClass=Sport";
-		expected = 
-				"{@type=korap:docGroup, operation=operation:and, operands=[" +
-					"{@type=korap:token, wrap={@type=korap:term, layer=lemma, key=Schwalbe, match=match:eq}}," +
-					"{@type=korap:doc, key=textClass, value=Sport, match=match:eq}" +
-				"]}";
-		cqt = new CollectionQueryTree();
-		cqt.process(query);
-		map = cqt.getRequestMap().get("collection").toString();
-		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		collection = "textClass=Sport | pubDate in 2014 & corpusId=WPD";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/@type").asText());
+		assertEquals("textClass", 		res.at("/collection/operands/0/key").asText());
+		assertEquals("Sport",			res.at("/collection/operands/0/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/0/match").asText());
+		assertEquals("korap:docGroup", 	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("pubDate",	 		res.at("/collection/operands/1/operands/0/key").asText());
+		assertEquals("2014",			res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("type:date",		res.at("/collection/operands/1/operands/0/type").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/0/match").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("corpusId", 		res.at("/collection/operands/1/operands/1/key").asText());
+		assertEquals("WPD",				res.at("/collection/operands/1/operands/1/value").asText());
+		assertEquals("match:eq",		res.at("/collection/operands/1/operands/1/match").asText());
+		
+		collection = "(textClass=Sport | pubDate in 2014) & corpusId=WPD";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/0/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operands/0/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/0/@type").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/1/@type").asText());
+		assertEquals("korap:doc",	 	res.at("/collection/operands/1/@type").asText());
+		
+		collection = "(textClass=Sport & pubDate in 2014) & corpusId=WPD";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/0/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/0/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/0/@type").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/1/@type").asText());
+		assertEquals("korap:doc",	 	res.at("/collection/operands/1/@type").asText());
+		
+		collection = "(textClass=Sport & textClass=ausland) | (corpusID=WPD & author=White)";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/0/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/0/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/0/@type").asText());
+		assertEquals("Sport",	 		res.at("/collection/operands/0/operands/0/value").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/1/@type").asText());
+		assertEquals("ausland",	 		res.at("/collection/operands/0/operands/1/value").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("WPD",		 		res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("White",	 		res.at("/collection/operands/1/operands/1/value").asText());
+		
+		collection = "(textClass=Sport & textClass=ausland) | (corpusID=WPD & author=White & pubDate in 2000)";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:docGroup", 	res.at("/collection/@type").asText());
+		assertEquals("operation:or",	res.at("/collection/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/0/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/0/operation").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operation").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/0/@type").asText());
+		assertEquals("Sport",	 		res.at("/collection/operands/0/operands/0/value").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/0/operands/1/@type").asText());
+		assertEquals("ausland",	 		res.at("/collection/operands/0/operands/1/value").asText());
+		assertEquals("korap:doc", 		res.at("/collection/operands/1/operands/0/@type").asText());
+		assertEquals("WPD",		 		res.at("/collection/operands/1/operands/0/value").asText());
+		assertEquals("korap:docGroup",	res.at("/collection/operands/1/operands/1/@type").asText());
+		assertEquals("operation:and",	res.at("/collection/operands/1/operands/1/operation").asText());
+		assertEquals("White",	 		res.at("/collection/operands/1/operands/1/operands/0/value").asText());
+		assertEquals("2000",	 		res.at("/collection/operands/1/operands/1/operands/1/value").asText());
 	}
 
+	@Test
+	public void testDateYear() throws QueryException, JsonProcessingException, IOException {
+		collection = "pubDate in 2000";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000",	 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:eq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate = 2000";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000",	 		res.at("/collection/value").asText());
+		assertEquals(true, 				res.at("/collection/type").isMissingNode());
+		assertEquals("match:eq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate since 2000";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000",	 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:geq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate until 2000";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000",	 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:leq", 		res.at("/collection/match").asText());
+	}
+	
+	@Test
+	public void testDateMonthDay() throws QueryException, JsonProcessingException, IOException {
+		collection = "pubDate in 2000-02";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000-02",	 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:eq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate = 2000-12";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000-12",	 		res.at("/collection/value").asText());
+		assertEquals(true, 				res.at("/collection/type").isMissingNode());
+		assertEquals("match:eq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate since 2000-02-01";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000-02-01", 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:geq", 		res.at("/collection/match").asText());
+		
+		collection = "pubDate until 2000-01-01";
+		qs.setQuery(query,ql);
+		qs.setCollection(collection);
+		res = mapper.readTree(qs.toJSON());
+		assertEquals("korap:doc", 		res.at("/collection/@type").asText());
+		assertEquals("pubDate", 		res.at("/collection/key").asText());
+		assertEquals("2000-01-01", 		res.at("/collection/value").asText());
+		assertEquals("type:date", 		res.at("/collection/type").asText());
+		assertEquals("match:leq", 		res.at("/collection/match").asText());
+	}
 }