Merge branch 'master' of ssh://korap.ids-mannheim.de:29418/KorAP/Koral
diff --git a/.gitignore b/.gitignore
index c304b58..f993ee1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,18 @@
 
 # /
+/sandbox
+/Sandbox
+/todo.org
+/target
+/.settings
 /.project
 /.classpath
-/.settings/org.eclipse.jdt.core.prefs
-/target
+*~
+.*
+!.gitignore
+
+
+/src/main/resources
 
 # /src/main/antlr/cosmas/
 /src/main/antlr/cosmas/c2ps_regex.g
diff --git a/Changes b/Changes
index ffea7ec..f58784d 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,13 @@
-0.22 2016-06-03
+0.22 2016-06-15
         - Spans now always wrap terms (diewald)
+	- Cosmas-II "und", "oder", and "nicht" operations
+	  now serialize to unordered sequences (diewald)
+	- Cleanup POM (diewald)
+	- Fix deserialization of unnecessary brackets
+	  around terms and termGroups in Poliqarp (diewald)
+	- Support for FCS 2.0 (margaretha)
+	- Fixed handling of escapes in regex
+	  (issue #21; diewald)
 
 0.21 2015-10-27
         - Improved meta query builder (hanl)
diff --git a/README.md b/README.md
index db6206e..e9ab53e 100644
--- a/README.md
+++ b/README.md
@@ -97,13 +97,20 @@
 
     git clone https://github.com/korap/Koral [install-dir]
     cd [install-dir]
-    mvn test
+    mvn test -Dhttps.protocols=TLSv1.2
     mvn install
 
 There is also a command line version. After installation, simply run
 
     java -jar target/Koral-0.2.jar [query] [queryLanguage]
-    
+   
+## Prerequisites
+
+* Java 7 (OpenJDK or Oracle JDK with [JCE] (http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html))
+* [Git](http://git-scm.com/)
+* At least [Maven 3.2.1](https://maven.apache.org/)
+* Further dependencies are resolved by Maven.
+
 ## Authorship
 
 Koral and KoralQuery were developed by Joachim Bingel,
diff --git a/pom.xml b/pom.xml
index 76d29f5..e9df54e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,219 +1,303 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <!-- <parent> <groupId>KorAP-modules</groupId> <artifactId>KorAP-core-modules</artifactId> 
-       <version>1.1</version> </parent> -->
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
 
-  <groupId>de.ids_mannheim.korap</groupId>
-  <artifactId>Koral</artifactId>
-  <version>0.22</version>
-  <packaging>jar</packaging>
-  <name>Koral</name>
-  <url>http://maven.apache.org</url>
-  <repositories>
-    <repository>
-      <id>id-maven-repo</id>
-      <url>http://maven.indexdata.com</url>
-    </repository>
-  </repositories>
-  <dependencies>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr4-runtime</artifactId>
-      <version>4.2</version>
-    </dependency>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr4-maven-plugin</artifactId>
-      <version>4.2</version>
-    </dependency>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr-runtime</artifactId>
-      <version>3.5</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>15.0</version>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-core</artifactId>
-      <version>2.3.3</version>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-annotations</artifactId>
-      <version>2.3.3</version>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-databind</artifactId>
-      <version>2.3.3</version>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.11</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>commons-lang</groupId>
-      <artifactId>commons-lang</artifactId>
-      <version>2.6</version>
-    </dependency>
-    <dependency>
-      <groupId>org.z3950.zing</groupId>
-      <artifactId>cql-java</artifactId>
-      <version>1.12</version>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-      <version>1.2.17</version>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>apache-log4j-extras</artifactId>
-      <version>1.2.17</version>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-      <version>1.7.5</version>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-      <version>1.7.5</version>
-    </dependency>
-  </dependencies>
-  <build>
-    <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
-    <outputDirectory>${basedir}/target/classes</outputDirectory>
-    <plugins>
-     <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.3</version>
-                <configuration>
-                    <compilerVersion>1.7</compilerVersion>
-                    <source>1.7</source>
-                    <target>1.7</target>
-                </configuration>
-            </plugin>
-      <!--
-	  Formatter plugin for Eclipse based coding conventions
-	  http://maven-java-formatter-plugin.googlecode.com/svn/site/0.4/usage.html
-      -->      
-      <plugin>
-	<groupId>com.googlecode.maven-java-formatter-plugin</groupId>
-	<artifactId>maven-java-formatter-plugin</artifactId>
-	<version>0.4</version>
-	<configuration>
-	  <configFile>${project.basedir}/Format.xml</configFile>
-	  <overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
-	  <compilerSource>1.7</compilerSource>
-	  <compilerCompliance>1.7</compilerCompliance>
-	  <compilerTargetPlatform>1.7</compilerTargetPlatform>
-	</configuration>
-      </plugin>
-      <plugin>
-	<artifactId>maven-dependency-plugin</artifactId>
-	<executions>
-	  <execution>
-	    <phase>install</phase>
-	    <goals>
-	      <goal>copy-dependencies</goal>
-	    </goals>
-	    <configuration>
-	      <outputDirectory>${project.build.directory}/../lib</outputDirectory>
-	    </configuration>
-	  </execution>
-	</executions>
-      </plugin>
-      <plugin>
-	<groupId>org.apache.maven.plugins</groupId>
-	<artifactId>maven-jar-plugin</artifactId>
-	<version>2.5</version>
-	<configuration>
-	  <archive>
-	    <manifest>
-	      <addClasspath>true</addClasspath>
-	      <classpathPrefix>../lib/</classpathPrefix>
-	      <mainClass>de.ids_mannheim.korap.query.serialize.QuerySerializer</mainClass>
-	    </manifest>
-	  </archive>
-	</configuration>
-      </plugin>
-      <plugin>
-	<!-- This plugin will help to build the ANTLR4 grammar on the fly. The 
-	     recipe is based on http://stackoverflow.com/questions/15310628/ customize-maven-to-automatically-create-antlr4-grammar-java-files-on-build -->
-	<groupId>org.antlr</groupId>
-	<artifactId>antlr4-maven-plugin</artifactId>
-	<version>4.2</version>
-	<executions>
-	  <execution>
-	    <id>poliqarpplus</id>
-	    <goals>
-	      <goal>antlr4</goal>
-	    </goals>
-	    <configuration>
-	      <sourceDirectory>${basedir}/src/main/antlr/poliqarpplus</sourceDirectory>
-	      <outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/poliqarpplus</outputDirectory>
-	      <libDirectory>${basedir}/src/main/antlr/poliqarpplus</libDirectory>
-	    </configuration>
-	    <phase>generate-sources</phase>
-	  </execution>
-	  <execution>
-	    <id>annis</id>
-	    <goals>
-	      <goal>antlr4</goal>
-	    </goals>
-	    <configuration>
-	      <sourceDirectory>${basedir}/src/main/antlr/annis</sourceDirectory>
-	      <outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/annis</outputDirectory>
-	      <libDirectory>${basedir}/src/main/antlr/annis</libDirectory>
-	    </configuration>
-	    <phase>generate-sources</phase>
-	  </execution>
-	  <execution>
-	    <id>collection</id>
-	    <goals>
-	      <goal>antlr4</goal>
-	    </goals>
-	    <configuration>
-	      <sourceDirectory>${basedir}/src/main/antlr/collection</sourceDirectory>
-	      <outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/collection</outputDirectory>
-	      <libDirectory>${basedir}/src/main/antlr/collection</libDirectory>
-	    </configuration>
-	    <phase>generate-sources</phase>
-	  </execution>
-	</executions>
-      </plugin>
-      <plugin>
-	<!-- This plugin will help to build the ANTLR3 grammar on the fly. The 
-	     recipe is based on http://stackoverflow.com/questions/15310628/ customize-maven-to-automatically-create-antlr4-grammar-java-files-on-build -->
-	<groupId>org.antlr</groupId>
-	<artifactId>antlr3-maven-plugin</artifactId>
-	<version>3.5.1</version>
-	<configuration>
-	  <sourceDirectory>${basedir}/src/main/antlr/cosmas</sourceDirectory>
-	  <outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/cosmas</outputDirectory>
-	  <libDirectory>${basedir}/src/main/antlr</libDirectory>
-	</configuration>
+	<groupId>de.ids_mannheim.korap</groupId>
+	<artifactId>Koral</artifactId>
+	<version>0.22</version>
+	<packaging>jar</packaging>
+	<name>Koral</name>
+	<url>http://maven.apache.org</url>
+
+	<organization>
+		<name>IDS Mannheim</name>
+		<url>http://www.ids-mannheim.de/</url>
+	</organization>
+
+	<developers>
+		<developer>
+			<name>Joachim Bingel</name>
+			<email>bingel@ids-mannheim.de</email>
+		</developer>
+		<developer>
+			<name>Nils Diewald</name>
+			<email>diewald@ids-mannheim.de</email>
+			<url>http://nils-diewald.de</url>
+		</developer>
+		<developer>
+			<name>Michael Hanl</name>
+			<email>hanl@ids-mannheim.de</email>
+		</developer>
+		<developer>
+			<name>Eliza Margaretha</name>
+			<email>margaretha@ids-mannheim.de</email>
+		</developer>
+	</developers>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>		
+	</properties>
 	
-	<executions>
-	  <execution>
-	    <goals>
-	      <goal>antlr</goal>
-	    </goals>
-	    <phase>generate-sources</phase>
-	  </execution>
-	</executions>
-      </plugin>
-    </plugins>
-  </build>
+	<repositories>
+		<repository>
+			<id>id-maven-repo</id>
+			<url>http://maven.indexdata.com</url>
+		</repository>
+		<repository>
+			<id>CLARIN</id>
+			<name>CLARIN Repository</name>
+			<url>https://nexus.clarin.eu/content/repositories/Clarin</url>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+	</repositories>
+	<dependencies>
+		<!-- <dependency> <groupId>org.antlr</groupId> <artifactId>antlr4-runtime</artifactId> 
+			<version>4.2</version> </dependency> -->
+		<dependency>
+			<groupId>org.antlr</groupId>
+			<artifactId>antlr4-runtime</artifactId>
+			<version>4.5.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.antlr</groupId>
+			<artifactId>antlr4-maven-plugin</artifactId>
+			<version>4.2</version>
+		</dependency>
+		<dependency>
+			<groupId>org.antlr</groupId>
+			<artifactId>antlr-runtime</artifactId>
+			<version>3.5</version>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>15.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-core</artifactId>
+			<version>2.3.3</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-annotations</artifactId>
+			<version>2.3.3</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+			<version>2.3.3</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.11</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.6</version>
+		</dependency>
+		<dependency>
+			<groupId>org.z3950.zing</groupId>
+			<artifactId>cql-java</artifactId>
+			<version>1.12</version>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>1.2.17</version>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>apache-log4j-extras</artifactId>
+			<version>1.2.17</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>1.7.5</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+			<version>1.7.5</version>
+		</dependency>
+		<dependency>
+			<groupId>eu.clarin.sru.fcs</groupId>
+			<artifactId>fcs-simple-endpoint</artifactId>
+			<version>1.3.0</version>
+		</dependency>
+	</dependencies>
+	<build>
+		<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
+		<outputDirectory>${basedir}/target/classes</outputDirectory>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.3</version>
+				<configuration>
+					<compilerVersion>1.7</compilerVersion>
+					<source>1.7</source>
+					<target>1.7</target>
+				</configuration>
+			</plugin>
+			<!-- <plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>properties-maven-plugin</artifactId>
+				<version>1.0.0</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>set-system-properties</goal>
+						</goals>
+						<configuration>
+							<properties>
+								<property>
+									<name>https.protocols</name>
+									<value>TLSv1.2</value>
+								</property>
+							</properties>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin> -->
+			<!-- <plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>2.19.1</version>
+				<configuration>
+					<systemProperties>
+			            <property>
+			              <name>https.protocols</name>
+			              <value>TLSv1.2</value>
+			            </property>
+			          </systemProperties>
+					<argLine>-Djava.https.protocols=TLSv1.2</argLine>
+				</configuration>
+			</plugin>
+			<plugin>
+		        <groupId>org.apache.maven.plugins</groupId>
+		        <artifactId>maven-failsafe-plugin</artifactId>
+		        <version>2.19.1</version>
+		        <configuration>
+		          <systemPropertyVariables>
+		            <https.protocols>TLSv1.2</https.protocols>
+		          </systemPropertyVariables>
+		        </configuration>
+	      </plugin> -->
+			<!-- Formatter plugin for Eclipse based coding conventions http://maven-java-formatter-plugin.googlecode.com/svn/site/0.4/usage.html -->
+			<plugin>
+				<groupId>com.googlecode.maven-java-formatter-plugin</groupId>
+				<artifactId>maven-java-formatter-plugin</artifactId>
+				<version>0.4</version>
+				<configuration>
+					<configFile>${project.basedir}/Format.xml</configFile>
+					<overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
+					<compilerSource>1.7</compilerSource>
+					<compilerCompliance>1.7</compilerCompliance>
+					<compilerTargetPlatform>1.7</compilerTargetPlatform>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>install</phase>
+						<goals>
+							<goal>copy-dependencies</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${project.build.directory}/../lib</outputDirectory>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<version>2.5</version>
+				<configuration>
+					<archive>
+						<manifest>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>../lib/</classpathPrefix>
+							<mainClass>de.ids_mannheim.korap.query.serialize.QuerySerializer</mainClass>
+						</manifest>
+					</archive>
+				</configuration>
+			</plugin>
+			<plugin>
+				<!-- This plugin will help to build the ANTLR4 grammar on the fly. The 
+					recipe is based on http://stackoverflow.com/questions/15310628/ customize-maven-to-automatically-create-antlr4-grammar-java-files-on-build -->
+				<groupId>org.antlr</groupId>
+				<artifactId>antlr4-maven-plugin</artifactId>
+				<version>4.2</version>
+				<executions>
+					<execution>
+						<id>poliqarpplus</id>
+						<goals>
+							<goal>antlr4</goal>
+						</goals>
+						<configuration>
+							<sourceDirectory>${basedir}/src/main/antlr/poliqarpplus</sourceDirectory>
+							<outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/poliqarpplus</outputDirectory>
+							<libDirectory>${basedir}/src/main/antlr/poliqarpplus</libDirectory>
+						</configuration>
+						<phase>generate-sources</phase>
+					</execution>
+					<execution>
+						<id>annis</id>
+						<goals>
+							<goal>antlr4</goal>
+						</goals>
+						<configuration>
+							<sourceDirectory>${basedir}/src/main/antlr/annis</sourceDirectory>
+							<outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/annis</outputDirectory>
+							<libDirectory>${basedir}/src/main/antlr/annis</libDirectory>
+						</configuration>
+						<phase>generate-sources</phase>
+					</execution>
+					<execution>
+						<id>collection</id>
+						<goals>
+							<goal>antlr4</goal>
+						</goals>
+						<configuration>
+							<sourceDirectory>${basedir}/src/main/antlr/collection</sourceDirectory>
+							<outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/collection</outputDirectory>
+							<libDirectory>${basedir}/src/main/antlr/collection</libDirectory>
+						</configuration>
+						<phase>generate-sources</phase>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<!-- This plugin will help to build the ANTLR3 grammar on the fly. The 
+					recipe is based on http://stackoverflow.com/questions/15310628/ customize-maven-to-automatically-create-antlr4-grammar-java-files-on-build -->
+				<groupId>org.antlr</groupId>
+				<artifactId>antlr3-maven-plugin</artifactId>
+				<version>3.5.1</version>
+				<configuration>
+					<sourceDirectory>${basedir}/src/main/antlr/cosmas</sourceDirectory>
+					<outputDirectory>${basedir}/src/main/java/de/ids_mannheim/korap/query/parse/cosmas</outputDirectory>
+					<libDirectory>${basedir}/src/main/antlr</libDirectory>
+				</configuration>
+				<executions>
+					<execution>
+						<goals>
+							<goal>antlr</goal>
+						</goals>
+						<phase>generate-sources</phase>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
 </project>
diff --git a/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4 b/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
index 68c0bef..b3bedb1 100644
--- a/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
+++ b/src/main/antlr/poliqarpplus/PoliqarpPlusLexer.g4
@@ -86,9 +86,13 @@
 EMPTYREL	: '@';
 
 /* Regular expressions and Regex queries */
-fragment RE_char     : ~('*' | '?' | '+' | '{' | '}' | '[' | ']'
-                     | '(' | ')' | '|' | '"' | ':' | '\'' | '\\');
+fragment RE_symbol     : ~('*' | '?' | '+' | '{' | '}' | '[' | ']'
+                     | '(' | ')' | '|' | '\\' | '"' | ':' | '\'');
+fragment RE_esc      : '\\' ('.' | '*' | '?' | '+' | '{' | '}' | '[' | ']'
+                     | '(' | ')' | '|' | '\\' | '"' | ':' | '\'');
+fragment RE_char     : (RE_symbol | RE_esc );
 fragment RE_alter    : ((RE_char | ('(' RE_expr ')') | RE_chgroup) '|' RE_expr )+;
+
 fragment RE_chgroup  : '[' RE_char+ ']';
 fragment RE_quant	 : (RE_star | RE_plus | RE_occ) QMARK?;
 fragment RE_opt      : (RE_char | RE_chgroup | ( '(' RE_expr ')')) '?';
@@ -100,4 +104,4 @@
 fragment RE_dquote            : '"'  (RE_expr | '\'' | ':' )* '"';
 fragment RE_squote            : '\''  (RE_expr | '\"' | ':' )* '\'';
  
-REGEX             : ( RE_dquote | RE_squote | );
+REGEX             : ( RE_dquote | RE_squote );
diff --git a/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4 b/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
index c48a3f9..571cf12 100644
--- a/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
+++ b/src/main/antlr/poliqarpplus/PoliqarpPlusParser.g4
@@ -43,9 +43,9 @@
 ;
 
 key
-: WORD
+: (WORD
 | regex
-| NUMBER
+| NUMBER)
 ;
 
 foundry
@@ -57,14 +57,13 @@
 ;
 
 value
-: WORD 
-| NUMBER
-| regex
+: (WORD | NUMBER) | regex
 ;
  
 /* Fields */
 term       
-: NEG* (foundry SLASH)? layer termOp key (COLON value)? flag? 
+: NEG* (foundry SLASH)? layer termOp key (COLON value)? flag?
+| LRPAREN term RRPAREN
 ;
 
 termOp
@@ -103,6 +102,7 @@
 
 termGroup
 : (term | LRPAREN termGroup RRPAREN) boolOp (term | LRPAREN termGroup RRPAREN | termGroup)
+| LRPAREN termGroup RRPAREN
 ;
 
 repetition
@@ -125,8 +125,7 @@
 ;
 
 span
-: LT ((foundry SLASH)? layer termOp)? key NEG* (LRPAREN term RRPAREN|LRPAREN termGroup RRPAREN)? GT
-| LT ((foundry SLASH)? layer termOp)? key NEG* (term|termGroup)? GT
+: LT ((foundry SLASH)? layer termOp)? key NEG* ((LRPAREN term RRPAREN|LRPAREN termGroup RRPAREN)? | (term|termGroup)?) GT
 ;
 
 position
@@ -150,7 +149,7 @@
 ;
 
 alignment
-: segment? (CARET segment)* CARET?
+: segment? ( (CARET segment)+ | CARET)
 ;
 
 disjunction
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralBoundary.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralBoundary.java
new file mode 100644
index 0000000..0faee0e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralBoundary.java
@@ -0,0 +1,46 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class KoralBoundary implements KoralObject {
+
+    private static final KoralType type = KoralType.BOUNDARY;
+
+    private int min;
+    private int max;
+
+    public KoralBoundary (int min, int max) {
+        this.min = min;
+        this.max = max;
+    }
+
+    public int getMin() {
+        return min;
+    }
+
+    public void setMin(int min) {
+        this.min = min;
+    }
+
+    public int getMax() {
+        return max;
+    }
+
+    public void setMax(int max) {
+        this.max = max;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        if (min > -1) {
+            map.put("min", getMin());
+        }
+        if (max > -1) {
+            map.put("max", getMax());
+        }
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
new file mode 100644
index 0000000..abdde0b
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
@@ -0,0 +1,22 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralContext {
+	SENTENCE("s"), PARAGRAPH("p"), TEXT("t");
+	
+	private final String key;
+	public static final String FOUNDRY ="base";
+	public static final String LAYER="s"; // surface
+	
+	KoralContext(String key){
+		this.key = key;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralDistance.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralDistance.java
new file mode 100644
index 0000000..eb88f58
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralDistance.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class KoralDistance implements KoralObject {
+
+    private final KoralType type = KoralType.DISTANCE;
+    private String key = "w";
+    private String foundry;
+    private String layer;
+    private KoralBoundary boundary;
+
+    public KoralDistance (KoralBoundary boundary) {
+        this.boundary = boundary;
+    }
+    
+    public KoralDistance (String key, KoralBoundary boundary) {            
+        this(boundary);
+        this.key = key;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getFoundry() {
+        return foundry;
+    }
+
+    public void setFoundry(String foundry) {
+        this.foundry = foundry;
+    }
+
+    public String getLayer() {
+        return layer;
+    }
+
+    public void setLayer(String layer) {
+        this.layer = layer;
+    }
+
+    public KoralBoundary getBoundary() {
+        return boundary;
+    }
+
+    public void setBoundary(KoralBoundary boundary) {
+        this.boundary = boundary;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> distanceMap = new LinkedHashMap<String, Object>();
+        distanceMap.put("@type", type.toString());
+        distanceMap.put("key", key);
+        if (foundry != null){
+            distanceMap.put("foundry", foundry);
+        }
+        if (layer!=null){
+            distanceMap.put("layer", layer);
+        }
+        distanceMap.put("boundary", boundary.buildMap());
+        return distanceMap;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java
new file mode 100644
index 0000000..b304f72
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java
@@ -0,0 +1,124 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.MapBuilder;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralOperation;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralGroup implements KoralObject {
+
+    private static final KoralType type = KoralType.GROUP;
+
+    private KoralOperation operation;
+
+    private boolean inOrder = false;
+    private List<KoralObject> operands;
+    private List<KoralDistance> distances;
+    private List<Frame> frames;
+    private KoralBoundary boundary;
+
+    public KoralGroup (KoralOperation operation) {
+        this.operation = operation;
+    }
+
+    public boolean isInOrder() {
+        return inOrder;
+    }
+
+    public void setInOrder(boolean inOrder) {
+        this.inOrder = inOrder;
+    }
+
+    public List<KoralObject> getOperands() {
+		return operands;
+	}
+    
+    public void setOperands(List<KoralObject> operands) {
+		this.operands = operands;
+	}
+
+    public KoralOperation getOperation() {
+        return operation;
+    }
+
+    public void setOperation(KoralOperation operation) {
+        this.operation = operation;
+    }
+
+    public List<KoralDistance> getDistances() {
+        return distances;
+    }
+
+    public void setDistances(List<KoralDistance> distances) {
+        this.distances = distances;
+    }
+    
+    public List<Frame> getFrames() {
+		return frames;
+	}
+
+	public void setFrames(List<Frame> frames) {
+		this.frames = frames;
+	}
+
+    public KoralBoundary getBoundary() {
+        return boundary;
+    }
+
+    public void setBoundary(KoralBoundary boundary) {
+        this.boundary = boundary;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        map.put("operation", operation.toString());
+
+        if (getDistances() != null) {
+            map.put("inOrder", isInOrder());
+            List<Map<String, Object>> distanceList = new ArrayList<Map<String, Object>>();
+            for (KoralDistance d : distances) {
+                distanceList.add(d.buildMap());
+            }
+            map.put("distances", distanceList);
+        }
+
+        List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
+        for (Object o : operands) {
+            operandList.add(MapBuilder.buildQueryMap(o));
+        }
+        map.put("operands", operandList);
+
+        if (boundary != null) {
+            map.put("boundary", boundary.buildMap());
+        }
+        return map;
+    }
+
+    public enum Frame{
+		SUCCEDS("succeeds"), SUCCEDS_DIRECTLY("succeedsDirectly"), OVERLAPS_RIGHT("overlapsRight"), 
+		ALIGNS_RIGHT("alignsRight"), IS_WITHIN("isWithin"), STARTS_WITH("startsWith"), 
+		MATCHES("matches"), ALIGNS_LEFT("alignsLeft"), IS_AROUND("isAround"), ENDS_WITH("endsWith"),
+		OVERLAPS_LEFT("overlapsLeft"), PRECEEDS_DIRECTLY("precedesDirectly"), PRECEDES("precedes");
+		
+		private String value;
+		Frame(String value) {
+			this.value = value;
+		}
+		
+		@Override
+		public String toString() {
+			return "frame:"+value;
+		}
+	}
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
new file mode 100644
index 0000000..bd5f6ea
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
@@ -0,0 +1,20 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralMatchOperator {
+    EQUALS("eq"), NOT_EQUALS("ne");
+
+    String value;
+
+    KoralMatchOperator (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "match:" + value;
+    };
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java
new file mode 100644
index 0000000..21fcf67
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java
@@ -0,0 +1,12 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.Map;
+
+/**
+ * @author margaretha
+ * 
+ */
+public interface KoralObject {
+
+    public Map<String, Object> buildMap();
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java
new file mode 100644
index 0000000..972bfd3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralOperation {
+    SEQUENCE, POSITION, DISJUNCTION, REPETITION, CLASS, MERGE, RELATION;
+
+    @Override
+    public String toString() {
+        return "operation:" + super.toString().toLowerCase();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java
new file mode 100644
index 0000000..84a1ff8
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralRelation {
+
+    AND, OR;
+
+    @Override
+    public String toString() {
+        return "relation:" + super.toString().toLowerCase();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java
new file mode 100644
index 0000000..4310bb9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java
@@ -0,0 +1,62 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+
+/**
+ * @author margaretha
+ *
+ */
+public class KoralSpan implements KoralObject{
+	
+	private static final KoralType type = KoralType.SPAN;
+	private final KoralObject wrappedObject;
+	private KoralObject attribute;
+	
+	public KoralSpan(KoralTerm term) throws KoralException {
+		if (term == null){
+			throw new KoralException(StatusCodes.MALFORMED_QUERY, "KoralSpan must not wrap null.");
+		}
+		this.wrappedObject = term;
+	}
+	
+	public KoralSpan(KoralTermGroup termGroup) throws KoralException {
+		if (termGroup == null){
+			throw new KoralException(StatusCodes.MALFORMED_QUERY,"KoralSpan must not wrap null.");
+		}
+		this.wrappedObject = termGroup;
+	}
+
+	public KoralObject getWrappedObject() {
+		return wrappedObject;
+	}
+	
+	public KoralObject getAttribute() {
+		return attribute;
+	}
+	
+	public void setAttribute(KoralTerm attribute) {
+		this.attribute = attribute;
+	}
+	
+	public void setAttribute(KoralTermGroup attributes) {
+		this.attribute = attributes;
+	}
+	
+	@Override
+	public Map<String, Object> buildMap() {
+		Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        if (wrappedObject == null){
+        	throw new NullPointerException("KoralSpan does not have a wrapped object.");
+        }
+        map.put("wrap", wrappedObject.buildMap());
+        if(attribute != null){
+        	map.put("wrap", attribute.buildMap());
+        }
+		return map;
+	}
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java
new file mode 100644
index 0000000..f9f60cd
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java
@@ -0,0 +1,138 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import de.ids_mannheim.korap.query.object.KoralMatchOperator;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralTerm implements KoralObject {
+
+    private static final KoralType koralType = KoralType.TERM;
+  
+    private final String key;
+    private String value;
+    private String layer;
+    private String foundry;
+    private KoralMatchOperator operator; // match
+    
+    private KoralTermType type;
+    
+    private boolean caseSensitive = true;
+
+    public KoralTerm(String key) throws KoralException {
+    	if (key == null){
+    		throw new KoralException(StatusCodes.MALFORMED_QUERY, 
+    				"KoralTerm key cannot be null.");
+    	}
+    	this.key = key;
+    }
+    
+    public KoralTerm(KoralContext context) throws KoralException {
+    	if (context.getKey() == null){
+    		throw new KoralException(StatusCodes.MALFORMED_QUERY, 
+    				"KoralTerm key cannot be null.");
+    	}
+    	this.key = context.getKey();
+    	this.foundry = KoralContext.FOUNDRY;
+    	this.layer = KoralContext.LAYER;
+	}
+    
+    public String getValue() {
+		return value;
+	}
+    
+    public void setValue(String value) {
+		this.value = value;
+	}
+    
+	public String getLayer() {
+        return layer;
+    }
+
+    public void setLayer(String layer) {
+        this.layer = layer;
+    }
+
+    public String getFoundry() {
+        return foundry;
+    }
+
+    public void setFoundry(String foundry) {
+        this.foundry = foundry;
+    }
+
+    public KoralMatchOperator getOperator() {
+		return operator;
+	}
+    
+    public void setOperator(KoralMatchOperator operator) {
+		this.operator = operator;
+	}
+
+    public String getKey() {
+        return key;
+    }
+
+    public KoralTermType getType() {
+        return type;
+    }
+
+    public void setType(KoralTermType regex) {
+        this.type = regex;
+    }
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public void setCaseSensitive(boolean isCaseSensitive) {
+        this.caseSensitive = isCaseSensitive;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", koralType.toString());
+        if (!isCaseSensitive()) {
+            map.put("caseInsensitive", "true");
+        }
+        
+        map.put("key", getKey());
+        if (value != null){
+        	map.put("value", getValue());
+        }
+        map.put("foundry", getFoundry());
+        map.put("layer", getLayer());
+        if (type != null){
+        	map.put("type", getType().toString());
+        }
+		if (operator !=null){
+			map.put("match", getOperator().toString());
+		}
+        return map;
+    }
+
+    public enum KoralTermType {
+        STRING("type:string"), REGEX("type:regex"), WILDCARD("type:wildcard"), PUNCT(
+                "type:punct");
+
+        String value;
+
+        KoralTermType (String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
new file mode 100644
index 0000000..eaae803
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
@@ -0,0 +1,57 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.MapBuilder;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralTermGroup implements KoralObject {
+
+    private static final KoralType type = KoralType.TERMGROUP;
+
+    private String relation;
+    private List<KoralObject> operands = new ArrayList<KoralObject>();
+
+    public KoralTermGroup (KoralRelation relation, List<KoralObject> operands)
+            throws KoralException {
+        this.relation = relation.toString();
+        this.operands = operands;
+    }
+
+    public String getRelation() {
+        return relation;
+    }
+
+    public void setRelation(String relation) {
+        this.relation = relation;
+    }
+
+    public List<KoralObject> getOperands() {
+        return operands;
+    }
+
+    public void setOperands(List<KoralObject> operands) {
+        this.operands = operands;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        map.put("relation", getRelation());
+
+        List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
+        for (Object o : getOperands()) {
+            operandList.add(MapBuilder.buildQueryMap(o));
+        }
+        map.put("operands", operandList);
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java
new file mode 100644
index 0000000..7798566
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java
@@ -0,0 +1,40 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralToken implements KoralObject {
+
+    private final static KoralType type = KoralType.TOKEN;
+    private KoralObject wrappedObject;
+
+    public KoralToken () {}
+    
+    public KoralToken (KoralObject wrappedObject) {
+        this.wrappedObject = wrappedObject;
+    }
+
+    public KoralObject getWrappedObject() {
+		return wrappedObject;
+	}
+    public void setWrappedObject(KoralObject wrappedObject) {
+		this.wrappedObject = wrappedObject;
+	}
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        if (wrappedObject != null){
+            map.put("wrap", wrappedObject.buildMap());
+        }
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
new file mode 100644
index 0000000..df7608f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralType {
+    TERMGROUP("koral:termGroup"), TERM("koral:term"), TOKEN("koral:token"), SPAN(
+            "koral:span"), GROUP("koral:group"), BOUNDARY("koral:boundary"), RELATION(
+            "koral:relation"), DISTANCE("koral:distance"), REFERENCE(
+            "koral:reference");
+
+    String value;
+
+    KoralType (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
new file mode 100644
index 0000000..42f5f83
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
@@ -0,0 +1,220 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import de.ids_mannheim.korap.query.object.KoralMatchOperator;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralRelation;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralTerm.KoralTermType;
+import de.ids_mannheim.korap.query.object.KoralTermGroup;
+import de.ids_mannheim.korap.query.object.KoralToken;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.fcs.parser.Expression;
+import eu.clarin.sru.server.fcs.parser.ExpressionAnd;
+import eu.clarin.sru.server.fcs.parser.ExpressionGroup;
+import eu.clarin.sru.server.fcs.parser.ExpressionNot;
+import eu.clarin.sru.server.fcs.parser.ExpressionOr;
+import eu.clarin.sru.server.fcs.parser.ExpressionWildcard;
+import eu.clarin.sru.server.fcs.parser.Operator;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.RegexFlag;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class ExpressionParser {
+
+    private static final String FOUNDRY_CNX = "cnx";
+    private static final String FOUNDRY_OPENNLP = "opennlp";
+    private static final String FOUNDRY_TT = "tt";
+    private static final String FOUNDRY_MATE = "mate";
+    private static final String FOUNDRY_XIP = "xip";
+
+    private List<String> supportedFoundries = Arrays
+            .asList(new String[] { FOUNDRY_CNX, FOUNDRY_OPENNLP, FOUNDRY_TT,
+                    FOUNDRY_MATE, FOUNDRY_XIP });
+
+    public KoralObject parseExpression(QueryNode queryNode) throws KoralException {
+        return parseExpression(queryNode, false, true);
+    }
+
+    public KoralObject parseExpression(QueryNode queryNode, boolean isNot,
+            boolean isToken) throws KoralException {
+
+        if (queryNode instanceof Expression) {
+            return parseSimpleExpression((Expression) queryNode, isNot, isToken);
+        }
+        else if (queryNode instanceof ExpressionAnd) {
+            List<QueryNode> operands = queryNode.getChildren();
+            if (isNot) {
+                return parseBooleanExpression(operands, KoralRelation.OR);
+            }
+            else {
+                return parseBooleanExpression(operands, KoralRelation.AND);
+            }
+        }
+        else if (queryNode instanceof ExpressionGroup) {
+            // Ignore the group
+            return parseExpression(queryNode.getFirstChild());
+        }
+        else if (queryNode instanceof ExpressionNot) {
+            boolean negation = isNot ? false : true;
+            return parseExpression(queryNode.getChild(0), negation, isToken);
+        }
+        else if (queryNode instanceof ExpressionOr) {
+            List<QueryNode> operands = queryNode.getChildren();
+            if (isNot) {
+                return parseBooleanExpression(operands, KoralRelation.AND);
+            }
+            else {
+                return parseBooleanExpression(operands, KoralRelation.OR);
+            }
+        }
+        else if (queryNode instanceof ExpressionWildcard) {
+            return new KoralToken();
+        }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Query is too complex.");
+        }
+    }
+
+    private KoralToken parseBooleanExpression(List<QueryNode> operands,
+            KoralRelation relation) throws KoralException {
+        List<KoralObject> terms = new ArrayList<>();
+        for (QueryNode node : operands) {
+            terms.add(parseExpression(node, false, false));
+        }
+        KoralTermGroup termGroup = new KoralTermGroup(relation, terms);
+        return new KoralToken(termGroup);
+    }
+
+    private KoralObject parseSimpleExpression(Expression expression, boolean isNot,
+            boolean isToken) throws KoralException {
+        KoralTerm koralTerm = parseTerm(expression, isNot);
+        if (isToken) {
+            return new KoralToken(koralTerm);
+        }
+        else {
+            return koralTerm;
+        }
+    }
+
+    public KoralTerm parseTerm(Expression expression, boolean isNot) throws KoralException {
+    	KoralTerm koralTerm = null;
+    	koralTerm = new KoralTerm(expression.getRegexValue());
+        koralTerm.setType(KoralTermType.REGEX);
+        parseLayerIdentifier(koralTerm, expression.getLayerIdentifier());
+        parseQualifier(koralTerm, expression.getLayerQualifier());
+        parseOperator(koralTerm, expression.getOperator(), isNot);
+        parseRegexFlags(koralTerm, expression.getRegexFlags());
+        return koralTerm;
+    }
+
+    private void parseLayerIdentifier(KoralTerm koralTerm, String identifier) throws KoralException {
+        String layer = null;
+        if (identifier == null) {
+            throw new KoralException(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 10: Layer identifier is missing.");
+        }
+        else if (identifier.equals("text")) {
+            layer = "orth";
+        }
+        else if (identifier.equals("pos")) {
+            layer = "p";
+        }
+        else if (identifier.equals("lemma")) {
+            layer = "l";
+        }
+        else {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Layer " + identifier
+                            + " is unsupported.");
+        }
+
+        koralTerm.setLayer(layer);
+    }
+
+    private void parseQualifier(KoralTerm koralTerm, String qualifier) throws KoralException {
+        String layer = koralTerm.getLayer();
+        if (layer == null) {
+            return;
+        }
+        // Set default foundry
+        if (qualifier == null) {
+            if (layer.equals("orth")) {
+                qualifier = FOUNDRY_OPENNLP;
+            }
+            else {
+                qualifier = FOUNDRY_TT;
+            }
+        }
+        else if (qualifier.equals(FOUNDRY_OPENNLP) && layer.equals("l")) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.");
+        }
+        else if (!supportedFoundries.contains(qualifier)) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Qualifier " + qualifier
+                            + " is unsupported.");
+        }
+
+        koralTerm.setFoundry(qualifier);
+    }
+
+    private void parseOperator(KoralTerm koralTerm, Operator operator,
+            boolean isNot) throws KoralException {
+    	KoralMatchOperator matchOperator = null;
+        if (operator == null || operator == Operator.EQUALS) {
+            matchOperator = isNot ? KoralMatchOperator.NOT_EQUALS : KoralMatchOperator.EQUALS;
+        }
+        else if (operator == Operator.NOT_EQUALS) {
+            matchOperator = isNot ? KoralMatchOperator.EQUALS : KoralMatchOperator.NOT_EQUALS;
+        }
+        else {
+        	throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 37:" + operator.name()
+                                    + " is unsupported.");
+        }
+        koralTerm.setOperator(matchOperator);
+    }
+
+    private void parseRegexFlags(KoralTerm koralTerm, Set<RegexFlag> set) throws KoralException {
+        // default case sensitive
+        if (set == null) return;
+        
+        ArrayList<String> names = new ArrayList<String>();
+        Iterator<RegexFlag> i = set.iterator();
+        while (i.hasNext()) {
+            RegexFlag f = i.next();
+            if (f == RegexFlag.CASE_SENSITVE) {
+                koralTerm.setCaseSensitive(true);
+            }
+            else if (f == RegexFlag.CASE_INSENSITVE) {
+                koralTerm.setCaseSensitive(false);
+            }
+            else {
+                names.add(f.name());
+            }
+        }
+
+        if (names.size() == 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflag: " + names.get(0)
+                            + " is unsupported.");
+        }
+        else if (names.size() > 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflags: " + names.toString()
+                            + " are unsupported.");
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
new file mode 100644
index 0000000..96e12b1
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
@@ -0,0 +1,242 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import de.ids_mannheim.korap.query.object.KoralContext;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import de.ids_mannheim.korap.query.object.KoralBoundary;
+import de.ids_mannheim.korap.query.object.KoralGroup;
+import de.ids_mannheim.korap.query.object.KoralDistance;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralOperation;
+import de.ids_mannheim.korap.query.object.KoralSpan;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralGroup.Frame;
+import eu.clarin.sru.server.fcs.parser.ExpressionWildcard;
+import eu.clarin.sru.server.fcs.parser.QueryDisjunction;
+import eu.clarin.sru.server.fcs.parser.QueryGroup;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.QuerySegment;
+import eu.clarin.sru.server.fcs.parser.QuerySequence;
+import eu.clarin.sru.server.fcs.parser.QueryWithWithin;
+import eu.clarin.sru.server.fcs.parser.SimpleWithin;
+import eu.clarin.sru.server.fcs.parser.SimpleWithin.Scope;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSSRUQueryParser {
+
+    private ExpressionParser expressionParser;
+
+    public FCSSRUQueryParser () {
+        this.expressionParser = new ExpressionParser();
+    }
+
+    public KoralObject parseQueryNode(QueryNode queryNode)
+            throws KoralException {
+
+        if (queryNode instanceof QuerySegment) {
+            return parseQuerySegment((QuerySegment) queryNode);
+        }
+        else if (queryNode instanceof QueryGroup) {
+            return parseQueryGroup((QueryGroup) queryNode);
+        }
+        else if (queryNode instanceof QuerySequence) {
+            return parseSequenceQuery(queryNode.getChildren());
+        }
+        else if (queryNode instanceof QueryDisjunction) {
+            return parseQueryDisjunction(queryNode.getChildren());
+        }
+        else if (queryNode instanceof QueryWithWithin) {
+            return parseWithinQuery((QueryWithWithin) queryNode);
+        }
+        else if (queryNode instanceof SimpleWithin) {
+            SimpleWithin withinNode = (SimpleWithin) queryNode;
+            return parseWithinScope(withinNode.getScope());
+        }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11:" + queryNode.getNodeType().name()
+                            + " is currently unsupported.");
+        }
+    }
+    
+    private KoralObject parseQuerySegment(QuerySegment segment)
+            throws KoralException {
+        KoralObject object = expressionParser.parseExpression(segment.getExpression());
+        return handleQuantifier(object, segment.getMinOccurs(), segment.getMaxOccurs());
+    }
+    
+    private KoralObject parseQueryGroup(QueryGroup group) throws KoralException {
+        KoralObject object = parseQueryNode(group.getFirstChild());
+        return handleQuantifier(object, group.getMinOccurs(), group.getMaxOccurs());
+    }
+    
+    private KoralObject handleQuantifier(KoralObject object, int minOccurs, int maxOccurs){
+        if ((minOccurs == 1) && (maxOccurs == 1)) {
+            return object;
+        }
+        
+        KoralBoundary boundary = new KoralBoundary(minOccurs, maxOccurs);
+        List<KoralObject> operand = new ArrayList<KoralObject>(1);
+        operand.add(object);
+
+        KoralGroup koralGroup = new KoralGroup(KoralOperation.REPETITION);
+        koralGroup.setBoundary(boundary);
+        koralGroup.setOperands(operand);
+        return koralGroup;
+    }
+    
+    private KoralObject parseWithinQuery(QueryWithWithin queryNode)
+            throws KoralException {
+        KoralGroup koralGroup = new KoralGroup(KoralOperation.POSITION);
+        koralGroup.setFrames(Arrays.asList(Frame.IS_AROUND));
+
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        operands.add(parseQueryNode(queryNode.getWithin()));
+        operands.add(parseQueryNode(queryNode.getQuery()));
+        koralGroup.setOperands(operands);
+        return koralGroup;
+    }
+
+    private KoralSpan parseWithinScope(Scope scope) throws KoralException {
+        if (scope == null) {
+            throw new KoralException(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 11: Within context is missing.");
+        }
+
+        KoralContext contextSpan;
+        if (scope == Scope.SENTENCE) {
+            contextSpan = KoralContext.SENTENCE;
+        }
+        else if (scope == Scope.PARAGRAPH) {
+            contextSpan = KoralContext.PARAGRAPH;
+        }
+        else if (scope == Scope.TEXT) {
+            contextSpan = KoralContext.TEXT;
+        }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Within scope " + scope.toString()
+                            + " is currently unsupported.");
+        }
+
+        return new KoralSpan(new KoralTerm(contextSpan));
+    }
+
+    private KoralGroup parseQueryDisjunction(List<QueryNode> children) throws KoralException {
+        KoralGroup koralGroup = new KoralGroup(KoralOperation.DISJUNCTION);
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        for (QueryNode child : children) {
+            operands.add(parseQueryNode(child));
+        }
+        koralGroup.setOperands(operands);
+        return koralGroup;
+    }
+
+    private KoralGroup parseSequenceQuery(List<QueryNode> children)
+            throws KoralException {
+        KoralGroup koralGroup = new KoralGroup(KoralOperation.SEQUENCE);
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        KoralObject operand;
+
+        boolean isEmptyTokenFound = false;
+        boolean isLastTokenEmpty = false;
+        int size = children.size();
+
+        for (int i = 0; i < size; i++) {
+            QueryNode child = children.get(i);
+            if (i > 0 && i < size - 1 && findEmptyToken(child)) {
+                QuerySegment qs = (QuerySegment) child;
+                if (isLastTokenEmpty) {
+                    updateBoundary(operands.get(operands.size() - 1), qs);
+                }
+                else {
+                    operands.add(new KoralBoundary(qs.getMinOccurs(), qs
+                            .getMaxOccurs()));
+                    isLastTokenEmpty = true;
+                }
+                isEmptyTokenFound = true;
+                continue;
+            }
+            operand = parseQueryNode(child);
+            operands.add(operand);
+            isLastTokenEmpty = false;
+        }
+
+        if (isEmptyTokenFound) {
+            operands = createDistance(koralGroup,operands);
+        }
+
+        koralGroup.setOperands(operands);
+        return koralGroup;
+    }
+
+    private boolean findEmptyToken(QueryNode child) {
+        if (child instanceof QuerySegment
+                && ((QuerySegment) child).getExpression() instanceof ExpressionWildcard) {
+            return true;
+        }
+        return false;
+    }
+
+    private void updateBoundary(KoralObject koralObject, QuerySegment qs) {
+        KoralBoundary boundary = (KoralBoundary) koralObject;
+        boundary.setMin(boundary.getMin() + qs.getMinOccurs());
+        boundary.setMax(boundary.getMax() + qs.getMaxOccurs());
+    }
+
+    private List<KoralObject> createDistance(KoralGroup koralGroup, List<KoralObject> operands){
+        boolean isSubGroupAdded = false;
+        List<KoralObject> newOperands = new ArrayList<KoralObject>(
+                operands.size());        
+        newOperands.add(operands.get(0));        
+        int operandSize = operands.size();
+        for (int i = 1; i < operandSize - 1; i++) {
+            KoralObject operand = operands.get(i);
+            if (operand instanceof KoralBoundary) {
+                List<KoralDistance> distances = new ArrayList<KoralDistance>();
+                distances.add(new KoralDistance ((KoralBoundary) operand));  
+                
+                if (koralGroup.getDistances() != null){
+                    KoralObject lastOperand = newOperands.get(newOperands.size()-1);
+                    KoralGroup subGroup = createSubGroup(distances, lastOperand, operands.get(i+1));
+                    newOperands.remove(lastOperand);
+                    newOperands.add(subGroup);
+                    isSubGroupAdded = true;
+                    continue;
+                }
+                else{                    
+                    koralGroup.setDistances(distances);
+                    koralGroup.setInOrder(true);
+                }
+            }
+            else{
+                newOperands.add(operand);                
+            }
+            isSubGroupAdded = false;
+        }
+        
+        if (!isSubGroupAdded){
+            newOperands.add(operands.get(operandSize-1));
+        }
+        return newOperands;
+    }
+    
+    private KoralGroup createSubGroup(List<KoralDistance> distances, 
+            KoralObject operand, KoralObject operand2) {
+        KoralGroup subGroup = new KoralGroup(KoralOperation.SEQUENCE);
+        subGroup.setDistances(distances);
+        subGroup.setInOrder(true);
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        operands.add(operand);
+        operands.add(operand2);
+        subGroup.setOperands(operands);
+        return subGroup;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessor.java
index 1baa50a..ae544a1 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessor.java
@@ -25,7 +25,7 @@
  * of the KoralQuery tree. See the official documentation for VC query
  * syntax
  * and functionality.
- *
+ * 
  * @author Michael Hanl (hanl@ids-mannheim.de)
  * @author Joachim Bingel (bingel@ids-mannheim.de)
  * @version 0.3.0
@@ -36,26 +36,31 @@
     private static Logger log = LoggerFactory
             .getLogger(CollectionQueryProcessor.class);
 
-    public CollectionQueryProcessor() {
+
+    public CollectionQueryProcessor () {
         KoralObjectGenerator.setQueryProcessor(this);
     }
 
-    public CollectionQueryProcessor(boolean verbose) {
+
+    public CollectionQueryProcessor (boolean verbose) {
         KoralObjectGenerator.setQueryProcessor(this);
         CollectionQueryProcessor.verbose = verbose;
     }
 
-    public CollectionQueryProcessor(String query) {
+
+    public CollectionQueryProcessor (String query) {
         KoralObjectGenerator.setQueryProcessor(this);
         process(query);
     }
 
+
     @Override
-    public void process(String query) {
+    public void process (String query) {
         ParseTree tree = parseCollectionQuery(query);
         if (this.parser != null) {
             super.parser = this.parser;
-        }else {
+        }
+        else {
             throw new NullPointerException("Parser has not been instantiated!");
         }
         log.info("Processing virtual collection query: " + query);
@@ -64,13 +69,15 @@
         if (tree != null) {
             log.debug("ANTLR parse tree: " + tree.toStringTree(parser));
             processNode(tree);
-        }else {
-            addError(StatusCodes.MALFORMED_QUERY,
-                    "Could not parse query >>> " + query + " <<<.");
+        }
+        else {
+            addError(StatusCodes.MALFORMED_QUERY, "Could not parse query >>> "
+                    + query + " <<<.");
         }
     }
 
-    private void processNode(ParseTree node) {
+
+    private void processNode (ParseTree node) {
         // Top-down processing
         String nodeCat = getNodeCat(node);
         openNodeCats.push(nodeCat);
@@ -90,8 +97,8 @@
          */
 
         if (nodeCat.equals("relation")) {
-            String operator = getNodeCat(node.getChild(1).getChild(0))
-                    .equals("&") ? "and" : "or";
+            String operator = getNodeCat(node.getChild(1).getChild(0)).equals(
+                    "&") ? "and" : "or";
             LinkedHashMap<String, Object> relationGroup = KoralObjectGenerator
                     .makeDocGroup(operator);
             putIntoSuperObject(relationGroup);
@@ -114,14 +121,14 @@
                 requestMap = new LinkedHashMap<String, Object>();
                 return;
             }
-//            if (QueryUtils.checkDateValidity(valueNode.getText())) {
-//                addWarning("The collection query contains a value that looks"
-//                        + " like a date ('" + valueNode.getText() + "') and an"
-//                        + " operator that is only defined for strings" + " ('"
-//                        + match + "'). The value is interpreted as"
-//                        + " a string. Use a date operator to ensure the value"
-//                        + " is treated as a date");
-//            }
+            //            if (QueryUtils.checkDateValidity(valueNode.getText())) {
+            //                addWarning("The collection query contains a value that looks"
+            //                        + " like a date ('" + valueNode.getText() + "') and an"
+            //                        + " operator that is only defined for strings" + " ('"
+            //                        + match + "'). The value is interpreted as"
+            //                        + " a string. Use a date operator to ensure the value"
+            //                        + " is treated as a date");
+            //            }
 
             putIntoSuperObject(term);
         }
@@ -192,7 +199,8 @@
                     }
                 }
                 token.put("wrap", term);
-            }else {
+            }
+            else {
                 // child is 'term' or 'termGroup' -> process in extra method 
                 LinkedHashMap<String, Object> termOrTermGroup = parseTermOrTermGroup(
                         node.getChild(1), negated);
@@ -231,18 +239,19 @@
 
     }
 
+
     /**
      * Checks whether the combination of operator and value is legal
      * (inequation operators <,>,<=,>= may only be used with dates).
      */
-    private boolean checkOperatorValueConformance(
+    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") || match
-                    .equals("match:containsnot"))) {
+            if (!(match.equals("match:eq") || match.equals("match:ne")
+                    || match.equals("match:contains") || match
+                        .equals("match:containsnot"))) {
                 addError(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
                         "You used an inequation operator with a string value.");
                 return false;
@@ -251,7 +260,8 @@
         return true;
     }
 
-    private LinkedHashMap<String, Object> parseValue(ParseTree valueNode) {
+
+    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");
@@ -275,15 +285,16 @@
         return map;
     }
 
+
     /**
      * Checks if value is a date
-     *
+     * 
      * @param valueNode
      * @return
      */
 
     @Deprecated
-    private boolean checkDateValidity(ParseTree valueNode) {
+    private boolean checkDateValidity (ParseTree valueNode) {
         Pattern p = Pattern.compile("[0-9]{4}(-([0-9]{2})(-([0-9]{2}))?)?");
         Matcher m = p.matcher(valueNode.getText());
 
@@ -294,7 +305,8 @@
         if (month != null) {
             if (Integer.parseInt(month) > 12) {
                 return false;
-            }else if (day != null) {
+            }
+            else if (day != null) {
                 if (Integer.parseInt(day) > 31) {
                     return false;
                 }
@@ -303,7 +315,8 @@
         return true;
     }
 
-    private String interpretMatchOperator(String match) {
+
+    private String interpretMatchOperator (String match) {
         String out = null;
         switch (match) {
             case "<":
@@ -351,8 +364,9 @@
         return out;
     }
 
+
     @Deprecated
-    private String invertInequation(String op) {
+    private String invertInequation (String op) {
         String inv = null;
         switch (op) {
             case "lt":
@@ -371,41 +385,47 @@
         return inv;
     }
 
-    private void putIntoSuperObject(LinkedHashMap<String, Object> object) {
+
+    private void putIntoSuperObject (LinkedHashMap<String, Object> object) {
         putIntoSuperObject(object, 0);
     }
 
+
     @SuppressWarnings({ "unchecked" })
-    private void putIntoSuperObject(LinkedHashMap<String, Object> object,
+    private void putIntoSuperObject (LinkedHashMap<String, Object> object,
             int objStackPosition) {
         if (objectStack.size() > objStackPosition) {
             ArrayList<Object> topObjectOperands = (ArrayList<Object>) objectStack
                     .get(objStackPosition).get("operands");
             topObjectOperands.add(object);
-        }else {
+        }
+        else {
             //        	requestMap = object;
             requestMap.put("collection", object);
         }
     }
 
-    private LinkedHashMap<String, Object> parseTermOrTermGroup(ParseTree node,
+
+    private LinkedHashMap<String, Object> parseTermOrTermGroup (ParseTree node,
             boolean negated) {
         return parseTermOrTermGroup(node, negated, "token");
     }
 
+
     /**
      * Parses a (term) or (termGroup) node
-     *
+     * 
      * @param node
-     * @param negatedGlobal Indicates whether the term/termGroup is globally
-     *                      negated, e.g. through a negation operator preceding
-     *                      the related token
-     *                      like "![base=foo]". Global negation affects the
-     *                      "match" parameter.
+     * @param negatedGlobal
+     *            Indicates whether the term/termGroup is globally
+     *            negated, e.g. through a negation operator preceding
+     *            the related token
+     *            like "![base=foo]". Global negation affects the
+     *            "match" parameter.
      * @return A term or termGroup object, depending on input
      */
     @SuppressWarnings("unchecked")
-    private LinkedHashMap<String, Object> parseTermOrTermGroup(ParseTree node,
+    private LinkedHashMap<String, Object> parseTermOrTermGroup (ParseTree node,
             boolean negatedGlobal, String mode) {
         if (getNodeCat(node).equals("term")) {
             String key = null;
@@ -479,7 +499,8 @@
                 }
             }
             return term;
-        }else {
+        }
+        else {
             // For termGroups, establish a boolean relation between operands 
             // and recursively call this function with the term or termGroup 
             // operands.
@@ -509,7 +530,8 @@
         }
     }
 
-    private ParserRuleContext parseCollectionQuery(String query) {
+
+    private ParserRuleContext parseCollectionQuery (String query) {
         Lexer lexer = new CollectionQueryLexer((CharStream) null);
         ParserRuleContext tree = null;
         Antlr4DescriptiveErrorListener errorListener = new Antlr4DescriptiveErrorListener(
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessor.java
index 1b22f39..fbf63ac 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessor.java
@@ -669,6 +669,7 @@
         // Step I: create group
         LinkedHashMap<String, Object> disjunction = KoralObjectGenerator
                 .makeGroup("disjunction");
+        disjunction.put("inOrder", false); // Order is not important 
         objectStack.push(disjunction);
         stackedObjects++;
         // Step II: decide where to put
@@ -681,6 +682,7 @@
         String nodeCat = getNodeCat(node);
         LinkedHashMap<String, Object> distgroup = KoralObjectGenerator
                 .makeGroup("sequence");
+        distgroup.put("inOrder", false); // Order is not important 
         ArrayList<Object> distances = new ArrayList<Object>();
         LinkedHashMap<String, Object> zerodistance = KoralObjectGenerator
                 .makeDistance("t", 0, 0);
@@ -1350,4 +1352,4 @@
         }
         return tree;
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
index b06731c..2dd8985 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
@@ -12,7 +12,7 @@
 
 /**
  * @author margaretha
- * @date 09.05.14
+ * @date 30.05.16
  */
 public class CqlQueryProcessor extends AbstractQueryProcessor {
 
@@ -22,51 +22,40 @@
     private static final String INDEX_WORDS = "words";
     private static final String TERM_RELATION_CQL_1_1 = "scr";
     private static final String TERM_RELATION_CQL_1_2 = "=";
-    private static final String SUPPORTED_RELATION_EXACT = "exact"; // not
-                                                                    // in
-                                                                    // the
-                                                                    // doc
+    private static final String SUPPORTED_RELATION_EXACT = "exact"; // not in the doc
     private static final String OPERATION_OR = "operation:or";
     private static final String OPERATION_SEQUENCE = "operation:sequence";
     private static final String OPERATION_POSITION = "operation:position";
-    private static final String KORAP_CONTEXT = "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld";
 
-    private LinkedHashMap<String, Object> requestMap;
     private String version;
     private boolean isCaseSensitive; // default true
 
-
     public CqlQueryProcessor (String query) {
         this(query, VERSION_1_2, true);
     }
 
-
     public CqlQueryProcessor (String query, String version) {
         this(query, version, true);
     }
 
-
     public CqlQueryProcessor (String query, String version,
-                              boolean isCaseSensitive) {
+            boolean isCaseSensitive) {
+        super();
         this.version = version;
         this.isCaseSensitive = isCaseSensitive;
-        this.requestMap = new LinkedHashMap<>();
-        requestMap.put("@context", KORAP_CONTEXT);
         process(query);
     }
 
-
     @Override
-    public Map<String, Object> getRequestMap () {
+    public Map<String, Object> getRequestMap() {
         return this.requestMap;
     }
 
-
     @Override
-    public void process (String query) {
+    public void process(String query) {
         if ((query == null) || query.isEmpty())
-            addError(StatusCodes.MALFORMED_QUERY,
-                    "SRU diagnostic 27: An empty query is unsupported.");
+            addError(StatusCodes.NO_QUERY,
+                    "SRU diagnostic 27: Empty query is unsupported.");
 
         CQLNode cqlNode = parseQuerytoCQLNode(query);
         Map<String, Object> queryMap = parseCQLNode(cqlNode);
@@ -74,8 +63,7 @@
         // requestMap.put("query", sentenceWrapper(queryMap));
     }
 
-
-    private Map<String, Object> sentenceWrapper (Map<String, Object> m) {
+    private Map<String, Object> sentenceWrapper(Map<String, Object> m) {
         Map<String, Object> map = new LinkedHashMap<String, Object>();
         map.put("@type", "koral:group");
         map.put("operation", OPERATION_POSITION);
@@ -93,8 +81,7 @@
         return map;
     }
 
-
-    private CQLNode parseQuerytoCQLNode (String query) {
+    private CQLNode parseQuerytoCQLNode(String query) {
         try {
             int compat = -1;
             switch (version) {
@@ -113,8 +100,7 @@
         }
     }
 
-
-    private Map<String, Object> parseCQLNode (CQLNode node) {
+    private Map<String, Object> parseCQLNode(CQLNode node) {
 
         if (node instanceof CQLTermNode) {
             return parseTermNode((CQLTermNode) node);
@@ -134,8 +120,7 @@
         }
     }
 
-
-    private Map<String, Object> parseTermNode (CQLTermNode node) {
+    private Map<String, Object> parseTermNode(CQLTermNode node) {
         checkTermNode(node);
         final String term = node.getTerm();
         if ((term == null) || term.isEmpty()) {
@@ -151,8 +136,7 @@
         }
     }
 
-
-    private Map<String, Object> parseAndNode (CQLAndNode node) {
+    private Map<String, Object> parseAndNode(CQLAndNode node) {
         checkBooleanModifier(node);
 
         Map<String, Object> map = new LinkedHashMap<String, Object>();
@@ -177,8 +161,7 @@
         return map;
     }
 
-
-    private Map<String, Object> parseOrNode (CQLOrNode node) {
+    private Map<String, Object> parseOrNode(CQLOrNode node) {
         checkBooleanModifier(node);
 
         Map<String, Object> map = new LinkedHashMap<String, Object>();
@@ -193,8 +176,7 @@
         return map;
     }
 
-
-    private Map<String, Object> writeSequence (String str) {
+    private Map<String, Object> writeSequence(String str) {
         Map<String, Object> sequenceMap = new LinkedHashMap<String, Object>();
         sequenceMap.put("@type", "koral:group");
         sequenceMap.put("operation", OPERATION_SEQUENCE);
@@ -209,8 +191,7 @@
         return sequenceMap;
     }
 
-
-    private Map<String, Object> writeTerm (String term) {
+    private Map<String, Object> writeTerm(String term) {
         Map<String, Object> map = new LinkedHashMap<String, Object>();
         map.put("@type", "koral:term");
         if (!isCaseSensitive) {
@@ -226,8 +207,7 @@
         return tokenMap;
     }
 
-
-    private void checkBooleanModifier (CQLBooleanNode node) {
+    private void checkBooleanModifier(CQLBooleanNode node) {
         List<Modifier> modifiers = node.getModifiers();
         if ((modifiers != null) && !modifiers.isEmpty()) {
             Modifier modifier = modifiers.get(0);
@@ -237,8 +217,7 @@
         }
     }
 
-
-    private void checkTermNode (CQLTermNode node) {
+    private void checkTermNode(CQLTermNode node) {
         // only allow "cql.serverChoice" and "words" index
         if (!(INDEX_CQL_SERVERCHOICE.equals(node.getIndex()) || INDEX_WORDS
                 .equals(node.getIndex()))) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
new file mode 100644
index 0000000..369e8e3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
@@ -0,0 +1,109 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.parse.fcsql.FCSSRUQueryParser;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.SRUQueryBase;
+import eu.clarin.sru.server.fcs.Constants;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.QueryParser;
+import eu.clarin.sru.server.fcs.parser.QueryParserException;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLQueryProcessor extends AbstractQueryProcessor {
+
+    public static final class FCSSRUQuery extends SRUQueryBase<QueryNode> {
+
+        private FCSSRUQuery (String rawQuery, QueryNode parsedQuery) {
+            super(rawQuery, parsedQuery);
+        }
+
+        @Override
+        public String getQueryType() {
+            return Constants.FCS_QUERY_TYPE_FCS;
+        }
+    }
+
+    private static final String VERSION_2_0 = "2.0";
+
+    private final QueryParser fcsParser = new QueryParser();
+    private String version;
+
+    public FCSQLQueryProcessor (String query, String version) {
+        super();
+        this.version = version;
+        process(query);
+    }
+
+    @Override
+    public Map<String, Object> getRequestMap() {
+        return this.requestMap;
+    }
+
+    @Override
+    public void process(String query) {
+        if (isVersionValid()) {
+            FCSSRUQuery fcsSruQuery = parseQueryStringtoFCSQuery(query);
+            if (fcsSruQuery != null) {
+                QueryNode fcsQueryNode = fcsSruQuery.getParsedQuery();
+                try {
+					parseFCSQueryToKoralQuery(fcsQueryNode);
+				} catch (KoralException e) {
+					addError(e.getStatusCode(), e.getMessage());
+				}
+            }
+        }
+    }
+
+    private boolean isVersionValid() {
+        if (version == null || version.isEmpty()) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 7: Version number is missing.");
+            return false;
+        }
+        else if (!version.equals(VERSION_2_0)) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 5: Only supports SRU version 2.0.");
+            return false;
+        }
+        return true;
+    }
+
+    private FCSSRUQuery parseQueryStringtoFCSQuery(String query) {
+        if ((query == null) || query.isEmpty())
+            addError(StatusCodes.NO_QUERY,
+                    "SRU diagnostic 1: No query has been passed.");
+        FCSSRUQuery fcsQuery = null;
+        try {
+            QueryNode parsedQuery = fcsParser.parse(query);
+            fcsQuery = new FCSSRUQuery(query, parsedQuery);
+            if (fcsQuery == null) {
+                addError(StatusCodes.UNKNOWN_QUERY_ERROR,
+                        "FCS diagnostic 10: Unexpected error while parsing query.");
+            }
+        }
+        catch (QueryParserException e) {
+            addError(
+                    StatusCodes.UNKNOWN_QUERY_ERROR,
+                    "FCS diagnostic 10: Query cannot be parsed, "
+                            + e.getMessage());
+        }
+        catch (Exception e) {
+            addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS diagnostic 10: "
+                    + "Unexpected error while parsing query.");
+        }
+        return fcsQuery;
+    }
+
+    private void parseFCSQueryToKoralQuery(QueryNode queryNode) throws KoralException {
+        FCSSRUQueryParser parser = new FCSSRUQueryParser();
+        Object o = parser.parseQueryNode(queryNode);
+        Map<String, Object> queryMap = MapBuilder.buildQueryMap(o);
+        if (queryMap != null) requestMap.put("query", queryMap);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
new file mode 100644
index 0000000..9d1c2ee
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
@@ -0,0 +1,42 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.object.KoralGroup;
+import de.ids_mannheim.korap.query.object.KoralSpan;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralTermGroup;
+import de.ids_mannheim.korap.query.object.KoralToken;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class MapBuilder {
+
+    public static Map<String, Object> buildQueryMap(Object o) {
+        if (o != null) {
+            if (o instanceof KoralToken) {
+                KoralToken token = (KoralToken) o;
+                return token.buildMap();
+            }
+            else if (o instanceof KoralGroup) {
+                KoralGroup group = (KoralGroup) o;
+                return group.buildMap();
+            }
+            if (o instanceof KoralTerm) {
+                KoralTerm term = (KoralTerm) o;
+                return term.buildMap();
+            }
+            else if (o instanceof KoralTermGroup) {
+                KoralTermGroup termGroup = (KoralTermGroup) o;
+                return termGroup.buildMap();
+            }
+            else if (o instanceof KoralSpan){
+            	KoralSpan span = (KoralSpan) o;
+            	return span.buildMap();
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/MetaQueryBuilder.java b/src/main/java/de/ids_mannheim/korap/query/serialize/MetaQueryBuilder.java
index cc51dd4..cad4319 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/MetaQueryBuilder.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/MetaQueryBuilder.java
@@ -17,41 +17,45 @@
     private Map meta;
     private SpanContext spanContext;
 
-    public MetaQueryBuilder() {
+
+    public MetaQueryBuilder () {
         this.meta = new LinkedHashMap();
         //        this.meta.put("fields", new LinkedList<>());
     }
 
+
     /**
      * context segment if context is either of type char or token.
      * size can differ for left and right span
-     *
+     * 
      * @param left
      * @param leftType
      * @param right
      * @param rightType
      * @return
      */
-    public MetaQueryBuilder setSpanContext(Integer left, String leftType,
+    public MetaQueryBuilder setSpanContext (Integer left, String leftType,
             Integer right, String rightType) {
         this.spanContext = new SpanContext(left, leftType, right, rightType);
         return this;
     }
 
-    public SpanContext getSpanContext() {
+
+    public SpanContext getSpanContext () {
         return this.spanContext;
     }
 
+
     /**
      * context if of type paragraph or sentence where left and right
      * size delimiters are irrelevant; or 2-token, 2-char p/paragraph,
      * s/sentence or token, char.
      * Distinguish
-     *
+     * 
      * @param context
      * @return
      */
-    public MetaQueryBuilder setSpanContext(String context) {
+    public MetaQueryBuilder setSpanContext (String context) {
         if (context != null) {
             if (!p.matcher(context).find())
                 this.spanContext = new SpanContext(context);
@@ -66,13 +70,15 @@
         return this;
     }
 
-    public MetaQueryBuilder addEntry(String name, Object value) {
+
+    public MetaQueryBuilder addEntry (String name, Object value) {
         if (value != null)
             meta.put(name, value);
         return this;
     }
 
-    public Map raw() {
+
+    public Map raw () {
         if (this.spanContext != null)
             meta.putAll(this.spanContext.raw());
         return meta;
@@ -85,24 +91,26 @@
         private int right_size;
         private String context = null;
 
+
         /**
          * context segment if context is either of type char or token.
          * size can differ for left and right span
-         *
+         * 
          * @param ls
          * @param lt
          * @param rs
          * @param rt
          * @return
          */
-        public SpanContext(int ls, String lt, int rs, String rt) {
+        public SpanContext (int ls, String lt, int rs, String rt) {
             this.left_type = lt;
             this.left_size = ls;
             this.right_type = rt;
             this.right_size = rs;
         }
 
-        public SpanContext(String context) {
+
+        public SpanContext (String context) {
             this.context = context;
         }
 
@@ -122,9 +130,6 @@
             return this.right_size;
         }
 
-
-
-
         public Map raw() {
             Map meta = new LinkedHashMap();
             if (this.context == null) {
@@ -138,7 +143,8 @@
                 r.add(this.right_size);
                 map.put("right", r);
                 meta.put("context", map);
-            }else
+            }
+            else
                 meta.put("context", this.context);
             return meta;
         }
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
index 4571efb..c0a88c9 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessor.java
@@ -770,8 +770,16 @@
     @SuppressWarnings("unchecked")
     private LinkedHashMap<String, Object> parseTermOrTermGroup (ParseTree node,
             boolean negatedGlobal, String mode) {
+
         String nodeCat = getNodeCat(node);
+
         if (nodeCat.equals("term")) {
+
+            // Term is defined recursive with non-necessary brackets
+            if (getNodeCat(node.getChild(0)).equals("(")) {
+                return parseTermOrTermGroup(node.getChild(1), negatedGlobal, mode);
+            };
+
             String key = null;
             LinkedHashMap<String, Object> term = KoralObjectGenerator
                     .makeTerm();
@@ -857,6 +865,12 @@
             return term;
         }
         else if (nodeCat.equals("termGroup")) {
+
+            // TermGroup is defined recursive with non-necessary brackets
+            if (getNodeCat(node.getChild(0)).equals("(")) {
+                return parseTermOrTermGroup(node.getChild(1), negatedGlobal, mode);
+            };
+
             // For termGroups, establish a boolean relation between
             // operands and recursively call this function with
             // the term or termGroup operands
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 fa4c029..62422ae 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
@@ -12,11 +12,10 @@
 import java.util.*;
 
 /**
- * Main class for Koral, serializes queries from concrete QLs to
- * KoralQuery
- *
- * @author Joachim Bingel (bingel@ids-mannheim.de),
- *         Michael Hanl (hanl@ids-mannheim.de)
+ * Main class for Koral, serializes queries from concrete QLs to KoralQuery
+ * 
+ * @author Joachim Bingel (bingel@ids-mannheim.de), Michael Hanl
+ *         (hanl@ids-mannheim.de), Eliza Margaretha (margaretha@ids-mannheim.de)
  * @version 0.3.0
  * @since 0.1.0
  */
@@ -31,8 +30,8 @@
 
     static {
         qlProcessorAssignment = new HashMap<String, Class<? extends AbstractQueryProcessor>>();
-        qlProcessorAssignment
-                .put("poliqarpplus", PoliqarpPlusQueryProcessor.class);
+		qlProcessorAssignment.put("poliqarpplus",
+				PoliqarpPlusQueryProcessor.class);
         qlProcessorAssignment.put("cosmas2", Cosmas2QueryProcessor.class);
         qlProcessorAssignment.put("annis", AnnisQueryProcessor.class);
         qlProcessorAssignment.put("cql", CqlQueryProcessor.class);
@@ -51,7 +50,7 @@
     private org.slf4j.Logger log = LoggerFactory
             .getLogger(QuerySerializer.class);
 
-    public QuerySerializer() {
+	public QuerySerializer() {
         this.errors = new LinkedList<>();
         this.warnings = new LinkedList<>();
         this.messages = new LinkedList<>();
@@ -60,7 +59,7 @@
     /**
      * @param args
      */
-    public static void main(String[] args) {
+	public static void main(String[] args) {
         /*
          * just for testing...
          */
@@ -73,7 +72,7 @@
             System.err
                     .println("Usage: QuerySerializer \"query\" queryLanguage");
             System.exit(1);
-        }else {
+		} else {
             queries = new String[] { args[0] };
             ql = args[1];
         }
@@ -82,95 +81,106 @@
             try {
                 jg.run(q, ql);
                 System.out.println();
-            }catch (NullPointerException npe) {
+			} catch (NullPointerException npe) {
                 npe.printStackTrace();
                 System.out.println("null\n");
-            }catch (IOException e) {
+			} catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 
     /**
-     * Runs the QuerySerializer by initializing the relevant
-     * AbstractSyntaxTree implementation (depending on specified query
-     * language) and transforms and writes the tree's requestMap to
-     * the specified output file.
-     *
-     * @param query         The query string
-     * @param queryLanguage The query language. As of 17 Dec 2014, this must be
-     *                      one of 'poliqarpplus', 'cosmas2', 'annis' or 'cql'.
-     * @throws IOException
-     */
-    public void run(String query, String queryLanguage) throws IOException {
+	 * Runs the QuerySerializer by initializing the relevant AbstractSyntaxTree
+	 * implementation (depending on specified query language) and transforms and
+	 * writes the tree's requestMap to the specified output file.
+	 * 
+	 * @param query
+	 *            The query string
+	 * @param queryLanguage
+	 *            The query language. As of 17 Dec 2014, this must be one of
+	 *            'poliqarpplus', 'cosmas2', 'annis' or 'cql'.
+	 * @throws IOException
+	 */
+	public void run(String query, String queryLanguage) throws IOException {
         if (queryLanguage.equalsIgnoreCase("poliqarp")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }else if (queryLanguage.equalsIgnoreCase("cosmas2")) {
+		} else if (queryLanguage.equalsIgnoreCase("cosmas2")) {
             ast = new Cosmas2QueryProcessor(query);
-        }else if (queryLanguage.equalsIgnoreCase("poliqarpplus")) {
+		} else if (queryLanguage.equalsIgnoreCase("poliqarpplus")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }else if (queryLanguage.equalsIgnoreCase("cql")) {
+		} else if (queryLanguage.equalsIgnoreCase("cql")) {
             ast = new CqlQueryProcessor(query);
+		} else if (queryLanguage.equalsIgnoreCase("fcsql")) {
+			ast = new FCSQLQueryProcessor(query, "2.0");
         }else if (queryLanguage.equalsIgnoreCase("annis")) {
             ast = new AnnisQueryProcessor(query);
-        }else {
-            throw new IllegalArgumentException(
-                    queryLanguage + " is not a supported query language!");
+		} else {
+			throw new IllegalArgumentException(queryLanguage
+					+ " is not a supported query language!");
         }
         toJSON();
     }
 
-    public QuerySerializer setQuery(String query, String ql, String version) {
+	public QuerySerializer setQuery(String query, String ql, String version) {
         ast = new DummyQueryProcessor();
         if (query == null || query.isEmpty()) {
             ast.addError(StatusCodes.NO_QUERY, "You did not specify a query!");
-        }else if (ql == null || ql.isEmpty()) {
+		} else if (ql == null || ql.isEmpty()) {
             ast.addError(StatusCodes.NO_QUERY,
                     "You did not specify any query language!");
-        }else if (ql.equalsIgnoreCase("poliqarp")) {
+		} else if (ql.equalsIgnoreCase("poliqarp")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }else if (ql.equalsIgnoreCase("cosmas2")) {
+		} else if (ql.equalsIgnoreCase("cosmas2")) {
             ast = new Cosmas2QueryProcessor(query);
-        }else if (ql.equalsIgnoreCase("poliqarpplus")) {
+		} else if (ql.equalsIgnoreCase("poliqarpplus")) {
             ast = new PoliqarpPlusQueryProcessor(query);
         }else if (ql.equalsIgnoreCase("cql")) {
-            if (version == null)
+			if (version == null) {
                 ast = new CqlQueryProcessor(query);
-            else
+			} else {
                 ast = new CqlQueryProcessor(query, version);
-        }else if (ql.equalsIgnoreCase("annis")) {
+			}
+		} else if (ql.equalsIgnoreCase("fcsql")) {
+			if (version == null) {
+				ast.addError(StatusCodes.MISSING_VERSION,
+						"SRU Version is missing!");
+			} else {
+				ast = new FCSQLQueryProcessor(query, version);
+			}
+		} else if (ql.equalsIgnoreCase("annis")) {
             ast = new AnnisQueryProcessor(query);
         }else {
-            ast.addError(StatusCodes.UNKNOWN_QL,
+			ast.addError(StatusCodes.UNKNOWN_QUERY_LANGUAGE,
                     ql + " is not a supported query language!");
         }
         return this;
     }
 
-    public QuerySerializer setQuery(String query, String ql) {
+	public QuerySerializer setQuery(String query, String ql) {
         return setQuery(query, ql, "");
     }
 
-    public void setVerbose(boolean verbose) {
+	public void setVerbose(boolean verbose) {
         AbstractQueryProcessor.verbose = verbose;
     }
 
-    public final String toJSON() {
+	public final String toJSON() {
         String ser;
         try {
             ser = mapper.writeValueAsString(raw());
             qllogger.info("Serialized query: " + ser);
-        }catch (JsonProcessingException e) {
+		} catch (JsonProcessingException e) {
             return "";
         }
         return ser;
     }
 
-    public final Map build() {
+	public final Map build() {
         return raw();
     }
 
-    private Map raw() {
+    private Map raw () {
         if (ast != null) {
             Map<String, Object> requestMap = new HashMap<>(ast.getRequestMap());
             Map meta = (Map) requestMap.get("meta");
@@ -211,33 +221,36 @@
         return new HashMap<>();
     }
 
-    private Map<String, Object> cleanup(Map<String, Object> requestMap) {
+    private Map<String, Object> cleanup (Map<String, Object> requestMap) {
         Iterator<Map.Entry<String, Object>> set = requestMap.entrySet()
                 .iterator();
         while (set.hasNext()) {
             Map.Entry<String, Object> entry = set.next();
-            if (entry.getValue() instanceof List && ((List) entry.getValue())
-                    .isEmpty())
+            if (entry.getValue() instanceof List
+                    && ((List) entry.getValue()).isEmpty())
                 set.remove();
-            else if (entry.getValue() instanceof Map && ((Map) entry.getValue())
-                    .isEmpty())
+            else if (entry.getValue() instanceof Map
+                    && ((Map) entry.getValue()).isEmpty())
                 set.remove();
-            else if (entry.getValue() instanceof String && ((String) entry
-                    .getValue()).isEmpty())
+            else if (entry.getValue() instanceof String
+                    && ((String) entry.getValue()).isEmpty())
                 set.remove();
         }
         return requestMap;
     }
 
-    private Map<String, Object> mergeCollection(Map<String, Object> collection1,
-            Map<String, Object> collection2) {
+	private Map<String, Object> mergeCollection (
+            Map<String, Object> collection1, Map<String, Object> collection2) {
         if (collection1 == null || collection1.isEmpty()) {
             return collection2;
-        }else if (collection2 == null || collection2.isEmpty()) {
+        }
+        else if (collection2 == null || collection2.isEmpty()) {
             return collection1;
-        }else if (collection1.equals(collection2)) {
+        }
+        else if (collection1.equals(collection2)) {
             return collection1;
-        }else {
+        }
+        else {
             LinkedHashMap<String, Object> docGroup = KoralObjectGenerator
                     .makeDocGroup("and");
             ArrayList<Object> operands = (ArrayList<Object>) docGroup
@@ -249,7 +262,7 @@
     }
 
     @Deprecated
-    public QuerySerializer addMeta(String cli, String cri, int cls, int crs,
+	public QuerySerializer addMeta(String cli, String cri, int cls, int crs,
             int num, int pageIndex) {
         MetaQueryBuilder meta = new MetaQueryBuilder();
         meta.setSpanContext(cls, cli, crs, cri);
@@ -259,12 +272,17 @@
         return this;
     }
 
-    public QuerySerializer setMeta(Map<String, Object> meta) {
+	public QuerySerializer setMeta(Map<String, Object> meta) {
         this.meta = meta;
         return this;
     }
 
-    public QuerySerializer setCollection(String collection) {
+	public QuerySerializer setMeta(MetaQueryBuilder meta) {
+		this.meta = meta.raw();
+		return this;
+	}
+
+	public QuerySerializer setCollection(String collection) {
         CollectionQueryProcessor tree = new CollectionQueryProcessor();
         tree.process(collection);
         Map<String, Object> collectionRequest = tree.getRequestMap();
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
index 6980b2e..47b5e09 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QueryUtils.java
@@ -14,7 +14,7 @@
  */
 public class QueryUtils {
 
-    public static SimpleEntry<String, Integer> checkUnbalancedPars(String q) {
+    public static SimpleEntry<String, Integer> checkUnbalancedPars (String q) {
         Map<Character, Character> brackets = new HashMap<Character, Character>();
         brackets.put('[', ']');
         brackets.put('{', '}');
@@ -31,8 +31,9 @@
             if (brackets.containsKey(q.charAt(i))) {
                 stack.push(q.charAt(i));
                 lastOpenBracket = i;
-            }else if (stack.empty() || (q.charAt(i) != brackets
-                    .get(stack.pop()))) {
+            }
+            else if (stack.empty()
+                    || (q.charAt(i) != brackets.get(stack.pop()))) {
                 return new SimpleEntry<String, Integer>(
                         "Parantheses/brackets unbalanced.", i);
             }
@@ -43,13 +44,15 @@
         return null;
     }
 
-    public static List<String> parseMorph(String stringTree) {
+
+    public static List<String> parseMorph (String stringTree) {
 
         ArrayList<String> morph = new ArrayList<String>();
         return morph;
     }
 
-    public static String buildCypherQuery(String cypher, String ctypel,
+
+    public static String buildCypherQuery (String cypher, String ctypel,
             String ctyper, int cl, int cr, int page, int limit) {
         // todo: implies that there is only one type allowed!
         String sctypel = "", sctyper = "";
@@ -100,7 +103,8 @@
         return buffer.toString();
     }
 
-    public static String buildDotQuery(long sid, String graphdb_id) {
+
+    public static String buildDotQuery (long sid, String graphdb_id) {
         StringBuffer b = new StringBuffer();
         b.append("<query>");
         b.append("<sentenceId>");
@@ -122,7 +126,8 @@
         return b.toString();
     }
 
-    public String buildaggreQuery(String query) {
+
+    public String buildaggreQuery (String query) {
         StringBuffer b = new StringBuffer();
         b.append("<query><cypher><![CDATA[");
         b.append(query);
@@ -151,9 +156,10 @@
         return b.toString();
     }
 
+
     @Deprecated
-    public static Map addParameters(Map request, int page, int num, String cli,
-            String cri, int cls, int crs, boolean cutoff) {
+    public static Map addParameters (Map request, int page, int num,
+            String cli, String cri, int cls, int crs, boolean cutoff) {
         Map ctx = new LinkedHashMap();
         List left = new ArrayList();
         left.add(cli);
@@ -172,14 +178,15 @@
         return request;
     }
 
+
     /**
      * Checks if value is a date
-     *
+     * 
      * @param value
      * @return
      */
 
-    public static boolean checkDateValidity(String value) {
+    public static boolean checkDateValidity (String value) {
         Pattern p = Pattern.compile("^[0-9]{4}(-([0-9]{2})(-([0-9]{2}))?)?$");
         Matcher m = p.matcher(value);
 
@@ -190,7 +197,8 @@
         if (month != null) {
             if (Integer.parseInt(month) > 12) {
                 return false;
-            }else if (day != null) {
+            }
+            else if (day != null) {
                 if (Integer.parseInt(day) > 31) {
                     return false;
                 }
@@ -199,7 +207,8 @@
         return true;
     }
 
-    public static String escapeRegexSpecialChars(String key) {
+
+    public static String escapeRegexSpecialChars (String key) {
         key.replace("\\", "\\\\");
         Pattern p = Pattern
                 .compile("\\.|\\^|\\$|\\||\\?|\\*|\\+|\\(|\\)|\\[|\\]|\\{|\\}");
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java
new file mode 100644
index 0000000..34d4569
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java
@@ -0,0 +1,30 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+public class KoralException extends Exception{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5463242042200109417L;
+	private int statusCode;
+	
+	public KoralException(int code, String message) {
+		super(message);
+		this.statusCode = code;
+	}
+	
+	public KoralException(int code, String message, Throwable cause) {
+        super(message, cause);
+        this.statusCode = code;
+    }
+
+	public int getStatusCode() {
+		return statusCode;
+	}
+
+	public void setStatusCode(int statusCode) {
+		this.statusCode = statusCode;
+	}
+
+	
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
index 10614ea..1f896b3 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
@@ -7,7 +7,9 @@
     public final static int INVALID_CLASS_REFERENCE = 304;
     public final static int INCOMPATIBLE_OPERATOR_AND_OPERAND = 305;
     public final static int UNKNOWN_QUERY_ELEMENT = 306;
-    public final static int UNKNOWN_QL = 307;
+	public final static int UNKNOWN_QUERY_LANGUAGE = 307;
     public final static int UNBOUND_ANNIS_RELATION = 308;
-    public final static int UNKNOWN_QUERY_ERROR = 399;
-}
+	public final static int MISSING_VERSION = 309;
+	public final static int QUERY_TOO_COMPLEX = 310;
+	public final static int UNKNOWN_QUERY_ERROR = 399;
+}
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryDuplicateTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryDuplicateTest.java
index ed8e40f..f7c4dd1 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryDuplicateTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryDuplicateTest.java
@@ -17,16 +17,20 @@
 public class CollectionQueryDuplicateTest {
 
     @Test
-    public void testCollectionQueryDuplicateThrowsAssertionException() throws IOException {
+    public void testCollectionQueryDuplicateThrowsAssertionException ()
+            throws IOException {
         QuerySerializer serializer = new QuerySerializer();
         serializer.setQuery("[base=Haus]", "poliqarp");
         serializer.setCollection("textClass=politik & corpusID=WPD");
         ObjectMapper m = new ObjectMapper();
         JsonNode first = m.readTree(serializer.toJSON());
         assertNotNull(first);
-        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON()).at("/collection"));
-        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON()).at("/collection"));
-        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON()).at("/collection"));
+        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON())
+                .at("/collection"));
+        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON())
+                .at("/collection"));
+        assertEquals(first.at("/collection"), m.readTree(serializer.toJSON())
+                .at("/collection"));
     }
 
 }
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessorTest.java
index 1a24352..0e91058 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/CollectionQueryProcessorTest.java
@@ -22,8 +22,9 @@
     ObjectMapper mapper = new ObjectMapper();
     JsonNode res;
 
+
     @Test
-    public void testContext() throws JsonProcessingException, IOException {
+    public void testContext () throws JsonProcessingException, IOException {
         collection = "textClass=politik";
         String contextString = "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld";
         qs.setQuery(query, ql);
@@ -32,8 +33,9 @@
         assertEquals(contextString, res.get("@context").asText());
     }
 
+
     @Test
-    public void testSimple() throws JsonProcessingException, IOException {
+    public void testSimple () throws JsonProcessingException, IOException {
         collection = "textClass=politik";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -53,9 +55,10 @@
         assertEquals("match:ne", res.at("/collection/match").asText());
     }
 
+
     @Test
-    public void testSpecialCharacters()
-            throws JsonProcessingException, IOException {
+    public void testSpecialCharacters () throws JsonProcessingException,
+            IOException {
         collection = "[base/n=alt]";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -71,8 +74,9 @@
 
     ;
 
+
     @Test
-    public void testContains() throws JsonProcessingException, IOException {
+    public void testContains () throws JsonProcessingException, IOException {
         collection = "title~Mannheim";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -101,8 +105,9 @@
         assertEquals("match:contains", res.at("/collection/match").asText());
     }
 
+
     @Test
-    public void testNotDate() throws JsonProcessingException, IOException {
+    public void testNotDate () throws JsonProcessingException, IOException {
         collection = "author=\"firefighter1974\"";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -115,29 +120,29 @@
         assertEquals("", res.at("/warnings/0/0").asText());
     }
 
+
     @Test
-    public void testTwoConjuncts() throws JsonProcessingException, IOException {
+    public void testTwoConjuncts () throws JsonProcessingException, IOException {
         collection = "textClass=Sport & pubDate in 2014";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:and", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:doc",
-                res.at("/collection/operands/1/@type").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral: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());
+        assertEquals("type:date", res.at("/collection/operands/1/type")
+                .asText());
+        assertEquals("match:eq", res.at("/collection/operands/1/match")
+                .asText());
 
         collection = "textClass=Sport & pubDate=2014";
         qs.setQuery(query, ql);
@@ -146,166 +151,165 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:and", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:doc",
-                res.at("/collection/operands/1/@type").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral: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());
+        assertEquals(true, res.at("/collection/operands/1/type")
+                .isMissingNode());
+        assertEquals("match:eq", res.at("/collection/operands/1/match")
+                .asText());
         assertTrue(res.at("/warnings/0").isMissingNode());
         //        assertTrue(res.at("/warnings/0/0").asText().startsWith(
         //                "The collection query contains a value that looks like a date"));
     }
 
+
     @Test
-    public void testThreeConjuncts()
-            throws JsonProcessingException, IOException {
+    public void testThreeConjuncts () throws JsonProcessingException,
+            IOException {
         collection = "textClass=Sport & pubDate in 2014 & corpusId=WPD";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:and", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/1/operation").asText());
         assertEquals("koral: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("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("koral: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("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 testTwoDisjuncts() throws JsonProcessingException, IOException {
+    public void testTwoDisjuncts () throws JsonProcessingException, IOException {
         collection = "textClass=Sport | pubDate in 2014";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:doc",
-                res.at("/collection/operands/1/@type").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral: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());
+        assertEquals("type:date", res.at("/collection/operands/1/type")
+                .asText());
+        assertEquals("match:eq", res.at("/collection/operands/1/match")
+                .asText());
     }
 
+
     @Test
-    public void testThreeDisjuncts()
-            throws JsonProcessingException, IOException {
+    public void testThreeDisjuncts () throws JsonProcessingException,
+            IOException {
         collection = "textClass=Sport | pubDate in 2014 | corpusId=WPD";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:or",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:or", res.at("/collection/operands/1/operation")
+                .asText());
         assertEquals("koral: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("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("koral: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("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 testMixed() throws JsonProcessingException, IOException {
+    public void testMixed () throws JsonProcessingException, IOException {
         collection = "textClass=Sport | (pubDate in 2014 & corpusId=WPD)";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/1/operation").asText());
         assertEquals("koral: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("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("koral: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("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());
 
@@ -315,33 +319,32 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("textClass",
-                res.at("/collection/operands/0/key").asText());
+        assertEquals("koral: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("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("match:eq", res.at("/collection/operands/0/match")
+                .asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/1/operation").asText());
         assertEquals("koral: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("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("koral: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("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());
 
@@ -351,16 +354,16 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:and", res.at("/collection/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("operation:or",
-                res.at("/collection/operands/0/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/0/@type")
+                .asText());
+        assertEquals("operation:or", res.at("/collection/operands/0/operation")
+                .asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/0/@type").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/1/@type").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/1/@type").asText());
+        assertEquals("koral:doc", res.at("/collection/operands/1/@type")
+                .asText());
 
         collection = "(textClass=Sport & pubDate in 2014) & corpusId=WPD";
         qs.setQuery(query, ql);
@@ -368,16 +371,16 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:and", res.at("/collection/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/0/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/0/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/0/operation").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/0/@type").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/1/@type").asText());
-        assertEquals("koral:doc",
-                res.at("/collection/operands/1/@type").asText());
+        assertEquals("koral:doc", res.at("/collection/operands/1/@type")
+                .asText());
 
         collection = "(textClass=Sport & textClass=ausland) | (corpusID=WPD & author=White)";
         qs.setQuery(query, ql);
@@ -385,30 +388,30 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/0/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/0/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/0/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/1/operation").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/0/@type").asText());
-        assertEquals("Sport",
-                res.at("/collection/operands/0/operands/0/value").asText());
+        assertEquals("Sport", res.at("/collection/operands/0/operands/0/value")
+                .asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/1/@type").asText());
         assertEquals("ausland",
                 res.at("/collection/operands/0/operands/1/value").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/1/operands/0/@type").asText());
-        assertEquals("WPD",
-                res.at("/collection/operands/1/operands/0/value").asText());
+        assertEquals("WPD", res.at("/collection/operands/1/operands/0/value")
+                .asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/1/operands/1/@type").asText());
-        assertEquals("White",
-                res.at("/collection/operands/1/operands/1/value").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);
@@ -416,26 +419,26 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:docGroup", res.at("/collection/@type").asText());
         assertEquals("operation:or", res.at("/collection/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/0/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/0/operation").asText());
-        assertEquals("koral:docGroup",
-                res.at("/collection/operands/1/@type").asText());
-        assertEquals("operation:and",
-                res.at("/collection/operands/1/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/0/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/0/operation").asText());
+        assertEquals("koral:docGroup", res.at("/collection/operands/1/@type")
+                .asText());
+        assertEquals("operation:and", res
+                .at("/collection/operands/1/operation").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/0/@type").asText());
-        assertEquals("Sport",
-                res.at("/collection/operands/0/operands/0/value").asText());
+        assertEquals("Sport", res.at("/collection/operands/0/operands/0/value")
+                .asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/0/operands/1/@type").asText());
         assertEquals("ausland",
                 res.at("/collection/operands/0/operands/1/value").asText());
         assertEquals("koral:doc",
                 res.at("/collection/operands/1/operands/0/@type").asText());
-        assertEquals("WPD",
-                res.at("/collection/operands/1/operands/0/value").asText());
+        assertEquals("WPD", res.at("/collection/operands/1/operands/0/value")
+                .asText());
         assertEquals("koral:docGroup",
                 res.at("/collection/operands/1/operands/1/@type").asText());
         assertEquals("operation:and",
@@ -448,8 +451,9 @@
                         .asText());
     }
 
+
     @Test
-    public void testDateYear() throws JsonProcessingException, IOException {
+    public void testDateYear () throws JsonProcessingException, IOException {
         collection = "pubDate in 2000";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -491,8 +495,9 @@
         assertEquals("match:leq", res.at("/collection/match").asText());
     }
 
+
     @Test
-    public void testDateMonthDay() throws JsonProcessingException, IOException {
+    public void testDateMonthDay () throws JsonProcessingException, IOException {
         collection = "pubDate in 2000-02";
         qs.setQuery(query, ql);
         qs.setCollection(collection);
@@ -534,8 +539,9 @@
         assertEquals("match:leq", res.at("/collection/match").asText());
     }
 
+
     @Test
-    public void testDateValidate() {
+    public void testDateValidate () {
         String fake_date = "fireStorm2014";
         String fake_date_2 = "2014-12Date";
         String date = "2015";
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessorTest.java
index 802ba9e..9db30b9 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/Cosmas2QueryProcessorTest.java
@@ -305,11 +305,17 @@
 
     @Test
     public void testOPORAND () throws JsonProcessingException, IOException {
+
+        // Query
         query = "(Sonne oder Mond) und scheint";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:group", res.at("/query/@type").asText());
         assertEquals("operation:sequence", res.at("/query/operation").asText());
+
+        assertEquals(false, res.at("/query/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/inOrder").asBoolean());
+
         assertEquals("cosmas:distance", res.at("/query/distances/0/@type")
                 .asText());
         assertEquals("t", res.at("/query/distances/0/key").asText());
@@ -318,6 +324,10 @@
         assertEquals("koral:group", res.at("/query/operands/0/@type").asText());
         assertEquals("operation:disjunction",
                 res.at("/query/operands/0/operation").asText());
+
+        assertEquals(false, res.at("/query/operands/0/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/operands/0/inOrder").asBoolean());
+
         assertEquals("Sonne", res.at("/query/operands/0/operands/0/wrap/key")
                 .asText());
         assertEquals("Mond", res.at("/query/operands/0/operands/1/wrap/key")
@@ -325,10 +335,15 @@
         assertEquals("koral:token", res.at("/query/operands/1/@type").asText());
         assertEquals("scheint", res.at("/query/operands/1/wrap/key").asText());
 
+        // Query
         query = "scheint und (Sonne oder Mond)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:group", res.at("/query/@type").asText());
+
+        assertEquals(false, res.at("/query/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/inOrder").asBoolean());
+
         assertEquals("operation:sequence", res.at("/query/operation").asText());
         assertEquals("cosmas:distance", res.at("/query/distances/0/@type")
                 .asText());
@@ -340,16 +355,25 @@
         assertEquals("koral:group", res.at("/query/operands/1/@type").asText());
         assertEquals("operation:disjunction",
                 res.at("/query/operands/1/operation").asText());
+
+        assertEquals(false, res.at("/query/operands/1/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/operands/1/inOrder").asBoolean());
+
         assertEquals("Sonne", res.at("/query/operands/1/operands/0/wrap/key")
                 .asText());
         assertEquals("Mond", res.at("/query/operands/1/operands/1/wrap/key")
                 .asText());
 
+        // Query
         query = "Regen und scheint und (Sonne oder Mond)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:group", res.at("/query/@type").asText());
         assertEquals("operation:sequence", res.at("/query/operation").asText());
+
+        assertEquals(false, res.at("/query/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/inOrder").asBoolean());
+
         assertEquals("cosmas:distance", res.at("/query/distances/0/@type")
                 .asText());
         assertEquals("t", res.at("/query/distances/0/key").asText());
@@ -357,8 +381,14 @@
         assertEquals(0, res.at("/query/distances/0/max").asInt());
         assertEquals("koral:token", res.at("/query/operands/0/@type").asText());
         assertEquals("Regen", res.at("/query/operands/0/wrap/key").asText());
-        assertEquals("koral:group", res.at("/query/@type").asText());
-        assertEquals("operation:sequence", res.at("/query/operation").asText());
+
+        assertEquals("koral:group", res.at("/query/operands/1/@type").asText());
+        assertEquals("operation:sequence", res
+                .at("/query/operands/1/operation").asText());
+
+        assertEquals(false, res.at("/query/operands/1/inOrder").isMissingNode());
+        assertEquals(false, res.at("/query/operands/1/inOrder").asBoolean());
+
         assertEquals("cosmas:distance",
                 res.at("/query/operands/1/distances/0/@type").asText());
         assertEquals("t", res.at("/query/operands/1/distances/0/key").asText());
@@ -368,6 +398,12 @@
                 .asText());
         assertEquals("koral:group", res
                 .at("/query/operands/1/operands/1/@type").asText());
+
+        assertEquals(false, res.at("/query/operands/1/operands/1/inOrder")
+                .isMissingNode());
+        assertEquals(false, res.at("/query/operands/1/operands/1/inOrder")
+                .asBoolean());
+
         assertEquals("operation:disjunction",
                 res.at("/query/operands/1/operands/1/operation").asText());
         assertEquals("Sonne",
@@ -728,8 +764,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/0/operands/0/@type")
                         .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/operands/0/wrap/key")
-                .asText());
+        assertEquals("s",
+                res.at("/query/operands/0/operands/0/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group", res
                 .at("/query/operands/0/operands/1/@type").asText());
         assertEquals("operation:class",
@@ -1401,7 +1438,8 @@
                         .asText());
         assertEquals("koral:span", res.at("/query/operands/0/operands/0/@type")
                 .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/wrap/key").asText());
+        assertEquals("s", res.at("/query/operands/0/operands/0/wrap/key")
+                .asText());
 
         query = "#BED(der Mann , +pe)";
         qs.setQuery(query, "cosmas2");
@@ -1425,8 +1463,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/0/operands/0/@type")
                         .asText());
-        assertEquals("p", res.at("/query/operands/0/operands/0/operands/0/wrap/key")
-                .asText());
+        assertEquals("p",
+                res.at("/query/operands/0/operands/0/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:reference",
                 res.at("/query/operands/0/operands/1/@type").asText());
         assertEquals("operation:focus",
@@ -1478,8 +1517,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/0/operands/0/@type")
                         .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/operands/0/wrap/key")
-                .asText());
+        assertEquals("s",
+                res.at("/query/operands/0/operands/0/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group",
                 res.at("/query/operands/0/operands/0/operands/1/@type")
                         .asText());
@@ -1513,8 +1553,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/1/operands/0/@type")
                         .asText());
-        assertEquals("p", res.at("/query/operands/0/operands/1/operands/0/wrap/key")
-                .asText());
+        assertEquals("p",
+                res.at("/query/operands/0/operands/1/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group",
                 res.at("/query/operands/0/operands/1/operands/1/@type")
                         .asText());
@@ -1567,7 +1608,8 @@
                         .asText());
         assertEquals("koral:span", res.at("/query/operands/0/operands/0/@type")
                 .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/wrap/key").asText());
+        assertEquals("s", res.at("/query/operands/0/operands/0/wrap/key")
+                .asText());
 
         query = "der:sa,-pa";
         qs.setQuery(query, "cosmas2");
@@ -1589,8 +1631,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/0/operands/0/@type")
                         .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/operands/0/wrap/key")
-                .asText());
+        assertEquals("s",
+                res.at("/query/operands/0/operands/0/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group",
                 res.at("/query/operands/0/operands/0/operands/1/@type")
                         .asText());
@@ -1615,8 +1658,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/1/operands/0/@type")
                         .asText());
-        assertEquals("p", res.at("/query/operands/0/operands/1/operands/0/wrap/key")
-                .asText());
+        assertEquals("p",
+                res.at("/query/operands/0/operands/1/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group",
                 res.at("/query/operands/0/operands/1/operands/1/@type")
                         .asText());
@@ -1651,8 +1695,9 @@
         assertEquals("koral:span",
                 res.at("/query/operands/0/operands/0/operands/0/@type")
                         .asText());
-        assertEquals("s", res.at("/query/operands/0/operands/0/operands/0/wrap/key")
-                .asText());
+        assertEquals("s",
+                res.at("/query/operands/0/operands/0/operands/0/wrap/key")
+                        .asText());
         assertEquals("koral:group",
                 res.at("/query/operands/0/operands/0/operands/1/@type")
                         .asText());
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
index 6f2bdd9..5af4b60 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
@@ -3,81 +3,58 @@
 import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
+import java.util.List;
 
 import org.junit.Test;
 import org.z3950.zing.cql.CQLParseException;
 
-import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import de.ids_mannheim.korap.query.serialize.CqlQueryProcessor;
-import de.ids_mannheim.korap.query.serialize.Cosmas2QueryProcessor;
-
-
 public class CqlQueryProcessorTest {
 
     String query;
-    String version = "1.2";
+    String VERSION = "1.2";
     ObjectMapper mapper = new ObjectMapper();
 
-
-    @Test
-    public void testExceptions () throws CQLParseException, IOException {
-        query = "(Kuh) prox (Germ) ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(48, errorCode);
-        }
-
-        query = "(Kuh) or/rel.combine=sum (Germ) ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(20, errorCode);
-        }
-
-        query = "dc.title any Germ ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(16, errorCode);
-        }
-
-        query = "cql.serverChoice any Germ ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(19, errorCode);
-        }
-
-        query = "";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(27, errorCode);
-        }
+    private List<Object> getError(CqlQueryProcessor processor) {
+        List<Object> errors = (List<Object>) processor.requestMap.get("errors");
+        return (List<Object>) errors.get(0);
     }
 
+    @Test
+    public void testExceptions() throws CQLParseException, IOException {
+        query = "(Kuh) prox (Germ) ";
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
+        List<Object> error = getError(cqlTree);
+        assertEquals(
+                "SRU diagnostic 48: Only basic search including term-only "
+                        + "and boolean (AND,OR) operator queries are currently supported.",
+                error.get(1));
+
+        query = "(Kuh) or/rel.combine=sum (Germ) ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals(
+                "SRU diagnostic 20: Relation modifier rel.combine = sum is not supported.",
+                error.get(1));
+
+        query = "dc.title any Germ ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 16: Index dc.title is not supported.",
+                error.get(1));
+
+        query = "cql.serverChoice any Germ ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 19: Relation any is not supported.",
+                error.get(1));
+
+        query = "";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 27: Empty query is unsupported.",
+                error.get(1));
+    }
 
     @Test
-    public void testAndQuery () throws CQLParseException, IOException,
-            Exception {
+    public void testAndQuery() throws CQLParseException, IOException, Exception {
         query = "(Sonne) and (scheint)";
         String jsonLd = "{@type : koral:group, operation : operation:sequence, inOrder : false,"
                 + "distances:[ "
@@ -87,19 +64,20 @@
                 + "{@type : koral:token,wrap : {@type : koral:term,key : scheint,layer : orth,match : match:eq}"
                 + "}]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
-        //			/System.out.println(serializedQuery);
-        //CosmasTree ct = new CosmasTree("Sonne und scheint");
-        //serializedQuery = mapper.writeValueAsString(ct.getRequestMap().get("query"));
-        //assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
+        // /System.out.println(serializedQuery);
+        // CosmasTree ct = new CosmasTree("Sonne und scheint");
+        // serializedQuery =
+        // mapper.writeValueAsString(ct.getRequestMap().get("query"));
+        // assertEquals(jsonLd.replace(" ", ""),
+        // serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testBooleanQuery () throws CQLParseException, IOException,
+    public void testBooleanQuery() throws CQLParseException, IOException,
             Exception {
         query = "((Sonne) or (Mond)) and (scheint)";
         String jsonLd = "{@type:koral:group, operation:operation:sequence, inOrder : false, distances:["
@@ -111,12 +89,11 @@
                 + "]},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:scheint, layer:orth, match:match:eq}}"
                 + "]}";
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
-
         query = "(scheint) and ((Sonne) or (Mond))";
         jsonLd = "{@type:koral:group, operation:operation:sequence, inOrder : false, distances:["
                 + "{@type:koral:distance, key:s, min:0, max:0}"
@@ -126,23 +103,22 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}" + "]}";
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
     }
 
-
     @Test
-    public void testOrQuery () throws CQLParseException, IOException, Exception {
+    public void testOrQuery() throws CQLParseException, IOException, Exception {
         query = "(Sonne) or (Mond)";
         String jsonLd = "{@type:koral:group, operation:operation:or, operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
@@ -156,7 +132,7 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}";
 
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
@@ -171,27 +147,25 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:scheint, layer:orth, match:match:eq}}"
                 + "]}" + "]}";
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testTermQuery () throws CQLParseException, IOException,
+    public void testTermQuery() throws CQLParseException, IOException,
             Exception {
         query = "Sonne";
         String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}}";
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testPhraseQuery () throws CQLParseException, IOException,
+    public void testPhraseQuery() throws CQLParseException, IOException,
             Exception {
         query = "\"der Mann\"";
         String jsonLd = "{@type:koral:group, operation:operation:sequence, operands:["
@@ -199,12 +173,11 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mann, layer:orth, match:match:eq}}"
                 + "]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
-
         query = "der Mann schläft";
         jsonLd = "{@type:koral:group, operation:operation:sequence, operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term, key:der, layer:orth, match:match:eq}},"
@@ -212,7 +185,7 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:schläft, layer:orth, match:match:eq}}"
                 + "]}";
 
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/EmptyResultsTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/EmptyResultsTest.java
index 58d775b..264ca67 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/EmptyResultsTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/EmptyResultsTest.java
@@ -15,8 +15,9 @@
 
     private static ObjectMapper mapper = new ObjectMapper();
 
+
     @Test
-    public void testEmptyQueryObject() {
+    public void testEmptyQueryObject () {
         QuerySerializer s = new QuerySerializer();
         s.setQuery("prox/unit=word/distance<=5", "cql");
         JsonNode node = mapper.valueToTree(s.build());
@@ -24,8 +25,9 @@
         assertEquals(node.has("collection"), false);
     }
 
+
     @Test
-    public void testEmptyCollectionObject() {
+    public void testEmptyCollectionObject () {
         QuerySerializer s = new QuerySerializer();
         s.setQuery("[base=Wort]", "poliqarp");
 
@@ -34,8 +36,9 @@
         assertEquals(node.has("collection"), false);
     }
 
+
     @Test
-    public void testEmptyMetaObject() {
+    public void testEmptyMetaObject () {
         QuerySerializer s = new QuerySerializer();
         s.setQuery("[base=Wort]", "poliqarp");
 
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
new file mode 100644
index 0000000..093d715
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
@@ -0,0 +1,416 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLComplexTest {
+
+    String query;
+    String jsonLd;
+    List<Object> error;
+
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    // | implicit-query
+    // | segment-query
+    // -------------------------------------------------------------------------
+    // implicit-query ::= flagged-regexp
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    @Test
+    public void testGroupQuery() throws IOException {
+        query = "(\"blaue\"|\"grüne\")";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";;
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // group and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"]|[text=\"Mann\"])";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+        // a group contains a sequence
+        query = "([text=\"blaue\"][pos=\"NN\"])";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // main-query ::= simple-query
+    // | simple-query "|" main-query /* or */
+    // | simple-query main-query /* sequence */
+    // | simple-query quantifier /* quatification */
+    // -------------------------------------------------------------------------
+
+    // | simple-query "|" main-query /* or */
+    @Test
+    public void testOrQuery() throws IOException {
+        query = "\"man\"|\"Mann\"";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:man,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos=\"NN\"]|\"Mann\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        // group with two segment queries
+        query = "[pos=\"NN\"]|[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // | simple-query main-query /* sequence */
+    @Test
+    public void testSequenceQuery() throws IOException {
+        query = "\"blaue|grüne\" [pos = \"NN\"]";
+        jsonLd = "{@type:koral:group, "
+                + "operation:operation:sequence, "
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term, key:blaue|grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, type:type:regex, match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[text=\"blaue|grüne\"][pos = \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"blaue\" \"grüne\" [pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+        // sequence and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"])[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:[{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // sequence of groups
+    }
+
+    // | simple-query quantifier /* quatification */
+    @Test
+    public void testSimpleQueryWithQuantifier() throws IOException {
+        // repetition
+        query = "\"die\"{2}";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:repetition,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:die,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}],"
+                + "boundary:{@type:koral:boundary,min:2,max:2}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"die\"{1,2}";
+        jsonLd = "{@type:koral:boundary,min:1,max:2}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"{,2}";
+        jsonLd = "{@type:koral:boundary,min:0,max:2}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"{2,}";
+        jsonLd = "{@type:koral:boundary,min:2}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"+";
+        jsonLd = "{@type:koral:boundary,min:1}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"?";
+        jsonLd = "{@type:koral:boundary,min:0, max:1}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"*";
+        jsonLd = "{@type:koral:boundary,min:0}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        query = "\"die\"{0}";
+        jsonLd = "{@type:koral:boundary,min:0, max:0}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/boundary", jsonLd);
+
+        // segment query with quantifier
+        query = "[cnx:pos=\"A\"]*";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:repetition,"
+                + "operands:[{@type:koral:token,wrap:{@type:koral:term,key:A,foundry:cnx,layer:p,type:type:regex,match:match:eq}}],"
+                + "boundary:{@type:koral:boundary,min:0}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testGroupQueryWithQuantifier() throws JsonProcessingException {
+        // group with quantifier
+        query = "(\"blaue\"|\"grüne\")*";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:repetition,"
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
+                + "]}]," + "boundary:{@type:koral:boundary,min:0}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // wildcards
+    @Test
+    public void testQueryWithEmptyToken() throws IOException {
+        // expansion query
+        query = "[]{2}\"Hund\"";
+        jsonLd = "{@type:koral:group, "
+                + "operation:operation:sequence, "
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:repetition,"
+                + "operands:["
+                + "{@type:koral:token}],"
+                + "boundary:{@type:koral:boundary,min:2,max:2}},"
+                + "{@type:koral:token, "
+                + "wrap:{@type:koral:term, key:Hund, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Hund\"[]{2}";
+        jsonLd = "{@type:koral:group," + "operation:operation:repetition,"
+                + "operands:[{@type:koral:token}],"
+                + "boundary:{@type:koral:boundary,min:2,max:2}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+        // arbitrary tokens
+        query = "[]{2}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // sequence with extension
+        query = "[cnx:pos=\"A\"] \"Hund\"[]{2}";
+        jsonLd = "["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:A,foundry:cnx,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Hund,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:group,operation:operation:repetition,operands:["
+                + "{@type:koral:token}],boundary:{@type:koral:boundary,min:2,max:2}}"
+                + "]";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/operands", jsonLd);
+    }
+
+    @Test
+    public void testDistanceQuery() throws IOException {
+        // distance query
+        query = "\"Katze\" [] \"Hund\"";
+        jsonLd = "{@type:koral:group,operation:operation:sequence,inOrder:true,"
+                + "distances:["
+                + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:1,max:1}}"
+                + "],"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Katze,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Hund,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testDistanceQueryWithQuantifier() throws IOException {
+        query = "\"Katze\" []* \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:0}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []+ \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:1}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []{3} \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:3,max:3}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []{2,3} \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:2,max:3}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+    }
+
+    @Test
+    public void testDistanceQueryWithMultipleWildcards() throws IOException {
+        // sequences of wildcards
+        query = "\"Katze\" []{3}[] \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:4,max:4}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []{3}[]? \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:3,max:4}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        query = "\"Katze\" []{2}[]{3}[] \"Hund\"";
+        jsonLd = "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:6,max:6}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/distances/0",
+                jsonLd);
+
+        // multiple occurrences of wildcards
+        query = "\"Katze\" []{3} \"Hund\" []{1,2} [cnx:pos=\"V\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "inOrder:true,"
+                + "distances:["
+                + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:3,max:3}}"
+                + "],"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Katze,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "inOrder:true,"
+                + "distances:["
+                + "{@type:koral:distance,key:w,boundary:{@type:koral:boundary,min:1,max:2}}"
+                + "],"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Hund,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:V,foundry:cnx,layer:p,type:type:regex,match:match:eq}}]}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // query ::= main-query within-part?
+    // -------------------------------------------------------------------------
+    // within-part ::= simple-within-part
+    // simple-within-part ::= "within" simple-within-scope
+
+    @Test
+    public void testWithinQuery() throws IOException {
+        query = "[cnx:pos=\"VVFIN\"] within s";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "operands:["
+                + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:VVFIN,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within sentence";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within p";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:p,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within text";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:t,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within u";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(310, error.get(0));
+        assertEquals(
+                "FCS diagnostic 11: Within scope UTTERANCE is currently unsupported.",
+                (String) error.get(1));
+    }
+
+    @Test
+    public void testWithinQueryWithEmptyTokens() throws IOException {
+        query = "[] within s";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "operands:["
+                + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
+                + "{@type:koral:token}" + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[]+ within s";
+        jsonLd = "{@type:koral:group," + "operation:operation:repetition,"
+                + "operands:[{@type:koral:token}],"
+                + "boundary:{@type:koral:boundary,min:1}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+    }
+
+    @Test
+    public void testWithinQueryWithGroupQuery() throws IOException {
+        query = "(\"blaue\"|\"grüne\")+ within s";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:repetition,"
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
+                + "]}]," + "boundary:{@type:koral:boundary,min:1}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+    }
+
+    @Test
+    public void testWrongQuery() throws IOException {
+        query = "!(mate:lemma=\"sein\" | mate:pos=\"PPOSS\")";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(399, error.get(0));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+
+        query = "![mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+
+        query = "(\"blaue\"&\"grüne\")";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+
+        query = "[pos=\"NN\"]&[text=\"Mann\"]";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(399, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("FCS diagnostic 10"));
+    }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
new file mode 100644
index 0000000..666bff1
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
@@ -0,0 +1,314 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLQueryProcessorTest {
+
+    static QuerySerializer qs = new QuerySerializer();
+    static ObjectMapper mapper = new ObjectMapper();
+    static JsonNode node;
+    String query;
+    String jsonLd;
+    List<Object> error;
+
+    public static void runAndValidate(String query, String jsonLd)
+            throws JsonProcessingException {
+        FCSQLQueryProcessor processor = new FCSQLQueryProcessor(query, "2.0");
+        String serializedQuery = mapper.writeValueAsString(processor
+                .getRequestMap().get("query"));
+        assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
+    }
+
+    public static void validateNode(String query, String path, String jsonLd)
+            throws JsonProcessingException, IOException {
+        qs.setQuery(query, "fcsql", "2.0");
+        node = mapper.readTree(qs.toJSON());
+        String serializedQuery = mapper.writeValueAsString(node.at(path));
+        assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
+    }
+
+    public static List<Object> getError(FCSQLQueryProcessor processor) {
+        List<Object> errors = (List<Object>) processor.requestMap.get("errors");
+        return (List<Object>) errors.get(0);
+    }
+
+    @Test
+    public void testVersion() throws JsonProcessingException {
+        error = getError(new FCSQLQueryProcessor("\"Sonne\"", "1.0"));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 5: Only supports SRU version 2.0.",
+                error.get(1));
+
+        error = getError(new FCSQLQueryProcessor("\"Sonne\"", null));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 7: Version number is missing.",
+                error.get(1));
+    }
+
+    // regexp ::= quoted-string
+    @Test
+    public void testTermQuery() throws JsonProcessingException {
+        query = "\"Sonne\"";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testRegex() throws JsonProcessingException {
+        query = "[text=\"M(a|ä)nn(er)?\"]";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
+                + "key:M(a|ä)nn(er)?,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\".*?Mann.*?\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:.*?Mann.*?,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"z.B.\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:z.B.,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"Sonne&scheint\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:Sonne&scheint,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        // Not possible
+        // query = "\"a\\.\"";
+    }
+
+    // flagged-regexp ::= regexp
+    // | regexp "/" regexp-flag+
+    @Test
+    public void testTermQueryWithRegexFlag() throws IOException {
+        query = "\"Fliegen\" /c";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
+                + "key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[text = \"Fliegen\" /i]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Fliegen\" /C";
+        jsonLd = "{@type:koral:term, key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /I";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /l";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(306, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("SRU diagnostic 48: Regexflags"));
+
+        query = "\"Fliegen\" /d";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Regexflag: IGNORE_DIACRITICS is unsupported.",
+                (String) error.get(1));
+    }
+
+    // operator ::= "=" /* equals */
+    // | "!=" /* non-equals */
+    @Test
+    public void testOperator() throws IOException {
+        query = "[cnx:pos != \"N\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, type:type:regex, match:match:ne}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    // attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+    // attribute ::= simple-attribute | qualified-attribute
+    // -------------------------------------------------------------------------
+
+    // simple-attribute ::= identifier
+    @Test
+    public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
+        query = "[text = \"Sonne\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[lemma = \"sein\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:tt, layer:l, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+                + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // qualified-attribute ::= identifier ":" identifier
+    @Test
+    public void testTermQueryWithQualifier() throws JsonProcessingException {
+        query = "[mate:lemma = \"sein\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:mate, layer:l, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos = \"N\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // expression ::= basic-expression
+    // | expression "|" expression /* or */
+    // | expression "&" expression /* and */
+    // -------------------------------------------------------------------------
+
+    // | expression "|" expression /* or */
+    @Test
+    public void testExpressionOr() throws IOException {
+        query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:or,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:lemma=\"sein\" | mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:term, key: sein, foundry: cnx, layer: l, type:type:regex, match: match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap/operands/0",
+                jsonLd);
+
+    }
+
+    // | expression "&" expression /* and */
+    @Test
+    public void testExpressionAnd() throws IOException {
+        query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:and,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // basic-expression ::= '(' expression ')' /* grouping */
+    // | "!" expression /* not */
+    // | attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+
+    // basic-expression ::= '(' expression ')' /* grouping */
+
+    @Test
+    public void testExpressionGroup() throws JsonProcessingException {
+        query = "[(text=\"blau\"|pos=\"ADJ\")]";
+        jsonLd = "{@type: koral:token,"
+                + "wrap: {@type: koral:termGroup,"
+                + "relation: relation:or,"
+                + "operands: ["
+                + "{@type: koral:term, key: blau, foundry: opennlp, layer: orth, type:type:regex,match: match:eq},"
+                + "{@type: koral:term, key: ADJ, foundry: tt, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testMultipleBooleanExpressions() throws IOException {
+        query = "[mate:lemma=\"sein\" & (mate:pos=\"PPOSS\"|mate:pos=\"VAFIN\")]";
+        jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:or,"
+                + " operands:["
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: VAFIN, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap/operands/1",
+                jsonLd);
+    }
+
+    // "!" expression /* not */
+    @Test
+    public void testExpressionNot() throws IOException {
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+                + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
+        query = "[!pos != \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+        query = "[!!pos = \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+        query = "[!!!pos != \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[mate:lemma=\"sein\" & !mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
+                + " wrap: { "
+                + "@type: koral:termGroup,"
+                + "relation: relation:and,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:ne}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+        
+        query = "[!(mate:lemma=\"sein\" & mate:pos=\"PPOSS\")]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testExceptions() throws JsonProcessingException {
+        // unsupported lemma und qualifier
+        query = "[opennlp:lemma = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.",
+                error.get(1));
+
+        query = "[tt:morph = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer morph is unsupported.",
+                error.get(1));
+
+        // unsupported qualifier
+        query = "[malt:lemma = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Qualifier malt is unsupported.",
+                error.get(1));
+
+        // unsupported layer
+        query = "[cnx:morph = \"heit\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer morph is unsupported.",
+                error.get(1));
+
+        // missing layer
+        query = "[cnx=\"V\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer cnx is unsupported.",
+                error.get(1));
+    }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
index fb82f28..167ee0e 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusQueryProcessorTest.java
@@ -155,18 +155,54 @@
         assertEquals("type:regex", res.at("/query/wrap/type").asText());
         assertEquals("orth", res.at("/query/wrap/layer").asText());
         assertEquals("match:eq", res.at("/query/wrap/match").asText());
+    }
 
+    @Test
+    public void testRegexEscape () throws JsonProcessingException, IOException {
         // Escape regex symbols
+        query = "\"a.+?\"";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
+        assertEquals("type:regex", res.at("/query/wrap/type").asText());
+        assertEquals("orth", res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("a.+?", res.at("/query/wrap/key").asText());
+
         query = "\"a\\.\"";
         qs.setQuery(query, "poliqarpplus");
         res = mapper.readTree(qs.toJSON());
-        System.out.println("QUERY IS  "+ res);
         assertEquals("koral:token", res.at("/query/@type").asText());
         assertEquals("koral:term", res.at("/query/wrap/@type").asText());
         assertEquals("type:regex", res.at("/query/wrap/type").asText());
         assertEquals("orth", res.at("/query/wrap/layer").asText());
         assertEquals("match:eq", res.at("/query/wrap/match").asText());
         assertEquals("a\\.", res.at("/query/wrap/key").asText());
+
+        query = "\"a\\.\\+\\?\\\\\"";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
+        assertEquals("type:regex", res.at("/query/wrap/type").asText());
+        assertEquals("orth", res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("a\\.\\+\\?\\\\", res.at("/query/wrap/key").asText());
+    }
+
+    @Test
+    public void testRegexWhiteSpace () throws JsonProcessingException, IOException {
+        // Escape regex symbols
+        query = "\"a b\"";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
+        assertEquals("type:regex", res.at("/query/wrap/type").asText());
+        assertEquals("orth", res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("a b", res.at("/query/wrap/key").asText());
     }
 
 
@@ -280,7 +316,7 @@
 
         query = "<\".*\">";
         qs.setQuery(query, "poliqarpplus");
-        res = mapper.readTree(qs.toJSON());
+       res = mapper.readTree(qs.toJSON());
         assertEquals("koral:span", res.at("/query/@type").asText());
         assertEquals(".*", res.at("/query/wrap/key").asText());
         assertEquals("type:regex", res.at("/query/wrap/type").asText());
@@ -658,7 +694,7 @@
         res = mapper.readTree(qs.toJSON());
         assertEquals("koral:token", res.at("/query/@type").asText());
         assertEquals(true, res.at("/query/key").isMissingNode());
-
+        
         query = "contains(<s>, [])";
         qs.setQuery(query, "poliqarpplus");
         res = mapper.readTree(qs.toJSON());
@@ -703,6 +739,78 @@
 
 
     @Test
+    public void testEmptyTokenSequence () throws JsonProcessingException, IOException {
+        query = "[]{3}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:group",
+                     res.at("/query/@type").asText());
+        assertEquals("operation:repetition",
+                     res.at("/query/operation").asText());
+        assertEquals("koral:token",
+                     res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:boundary",
+                     res.at("/query/boundary/@type").asText());
+        assertEquals(3,
+                     res.at("/query/boundary/min").asInt());
+        assertEquals(3,
+                     res.at("/query/boundary/max").asInt());
+
+        query = "[]{1,3}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:group",
+                     res.at("/query/@type").asText());
+        assertEquals("operation:repetition",
+                     res.at("/query/operation").asText());
+        assertEquals("koral:token",
+                     res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:boundary",
+                     res.at("/query/boundary/@type").asText());
+        assertEquals(1,
+                     res.at("/query/boundary/min").asInt());
+        assertEquals(3,
+                     res.at("/query/boundary/max").asInt());
+
+
+        query = "[]{ , 3}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:group",
+                     res.at("/query/@type").asText());
+        assertEquals("operation:repetition",
+                     res.at("/query/operation").asText());
+        assertEquals("koral:token",
+                     res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:boundary",
+                     res.at("/query/boundary/@type").asText());
+        assertEquals(0,
+                     res.at("/query/boundary/min").asInt());
+        assertEquals(3,
+                     res.at("/query/boundary/max").asInt());
+
+        query = "[]{4,}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+
+        assertEquals("koral:group",
+                     res.at("/query/@type").asText());
+        assertEquals("operation:repetition",
+                     res.at("/query/operation").asText());
+        assertEquals("koral:token",
+                     res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:boundary",
+                     res.at("/query/boundary/@type").asText());
+        assertEquals(4,
+                     res.at("/query/boundary/min").asInt());
+        assertTrue(res.at("/query/boundary/max").isMissingNode());
+    }
+
+
+    @Test
     public void testLeadingTrailingEmptyTokens ()
             throws JsonProcessingException, IOException {
         query = "[][base=Mann]";
@@ -726,6 +834,24 @@
         assertEquals("koral:token", operands.get(0).at("/@type").asText());
         assertEquals(true, operands.get(0).at("/key").isMissingNode());
 
+        query = "[base=Mann][]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        operands = Lists.newArrayList(res.at("/query/operands").elements());
+        assertEquals("koral:token", operands.get(1).at("/@type").asText());
+        assertEquals(true, operands.get(1).at("/key").isMissingNode());
+
+        query = "[base=Mann][]{3}";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        operands = Lists.newArrayList(res.at("/query/operands").elements());
+        res = operands.get(1);
+        assertEquals("koral:group", res.at("/@type").asText());
+        assertEquals(true, res.at("/key").isMissingNode());
+        assertEquals("operation:repetition", res.at("/operation").asText());
+        assertEquals(3, res.at("/boundary/min").asInt());
+        assertEquals(3, res.at("/boundary/max").asInt());
+
         query = "startswith(<s>, [][base=Mann])";
         qs.setQuery(query, "poliqarpplus");
         res = mapper.readTree(qs.toJSON());
@@ -976,6 +1102,79 @@
                 .asText());
     }
 
+    @Test
+    public void testUnnecessaryParentheses () throws JsonProcessingException,
+            IOException {
+        query = "[(base=Mann)]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("Mann", res.at("/query/wrap/key").asText());
+        assertEquals("lemma", res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+
+        query = "[(((base=Mann)))]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("Mann", res.at("/query/wrap/key").asText());
+        assertEquals("lemma", res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+
+        query = "[(base=Mann&cas=N)]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:termGroup", res.at("/query/wrap/@type")
+                .asText());
+        assertEquals("relation:and", res.at("/query/wrap/relation")
+                     .asText());
+        assertEquals("Mann", res.at("/query/wrap/operands/0/key")
+                     .asText());
+        assertEquals("lemma", res.at("/query/wrap/operands/0/layer")
+                     .asText());
+        assertEquals("N", res.at("/query/wrap/operands/1/key")
+                     .asText());
+        assertEquals("cas", res.at("/query/wrap/operands/1/layer")
+                     .asText());
+
+
+        query = "[(((base=Mann&cas=N)))]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:termGroup", res.at("/query/wrap/@type")
+                .asText());
+        assertEquals("relation:and", res.at("/query/wrap/relation")
+                     .asText());
+        assertEquals("Mann", res.at("/query/wrap/operands/0/key")
+                     .asText());
+        assertEquals("lemma", res.at("/query/wrap/operands/0/layer")
+                     .asText());
+        assertEquals("N", res.at("/query/wrap/operands/1/key")
+                     .asText());
+        assertEquals("cas", res.at("/query/wrap/operands/1/layer")
+                     .asText());
+
+
+        query = "[(((base=Mann&((cas=N)))))]";
+        qs.setQuery(query, "poliqarpplus");
+        res = mapper.readTree(qs.toJSON());
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:termGroup", res.at("/query/wrap/@type")
+                .asText());
+        assertEquals("relation:and", res.at("/query/wrap/relation")
+                     .asText());
+        assertEquals("Mann", res.at("/query/wrap/operands/0/key")
+                     .asText());
+        assertEquals("lemma", res.at("/query/wrap/operands/0/layer")
+                     .asText());
+        assertEquals("N", res.at("/query/wrap/operands/1/key")
+                     .asText());
+        assertEquals("cas", res.at("/query/wrap/operands/1/layer")
+                     .asText());
+
+    };
 
     @Test
     public void testTokenSequence () throws JsonProcessingException,
@@ -1703,8 +1902,10 @@
         assertEquals("VVFIN", res.at("/query/operands/1/wrap/key").asText());
     }
 
+
     @Test
-    public void testSpanSerialization () throws JsonProcessingException, IOException {
+    public void testSpanSerialization () throws JsonProcessingException,
+            IOException {
 
         // Both constructs should be serialized identically
         query = "contains(<s>, der)";