Operator #ELEM(Elementname) in C2QL: missing default layer 's' is added.
Op. #ELEM(w ana='...'): either elementname 'W' or attribute name 'ana' generates layer=p.
Op. #ELEM with attributes: layer, key and value corrected.
Op. #ELEM: several attributes and several attribute values: OK.
Op. #ELEM(w ana='...' type='...'): mixted combination of 'ana' with
other attributes.
Op. #ELEM(w ana="NOU -PL'): negation for attribute values.
Op. #ELEM(foundry/layer ... ): added att=val pairs.
Tests added for all theses cases.

Change-Id: Ib783d35ee4d84b56af5a99dbd0c37b55cdde2685
Reviewed-on: https://korap.ids-mannheim.de/gerrit/c/KorAP/Koral/+/10705
Reviewed-by: Nils Diewald <nils@diewald-online.de>
Reviewed-by: <bodmer@ids-mannheim.de>
diff --git a/Changes b/Changes
index 138ffe1..a083c3b 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,19 @@
+0.51 2026-07-29
+     - [feature] C2QL MORPH(v1 v2..): more than 1 value with/without foundry/layer specification works now.
+     - [feature] translating MORPH expressions from C2-style STTS/CONNEXOR to STTS/marmot.
+     - [bugfix] C2QL: correcting #BED() and word:sa/se... (due to MORPH modifications).
+     - [feature] JUnits added.
+
+0.50 2026-07-29
+     - [bugfix] Op. #ELEM() in C2QL: missing default layer 's' is added.
+     - [bugfix] Op. #ELEM(w ana='...'): either 'W' or 'ana' generates layer=p.
+     - [bugfix] Op. #ELEM() serializaton of attributes: layer, key and value corrected.
+     - [bugfix] Op. #ELEM(w ana='...' type='...'): mixed combination of 'ana' with other attr. corrected.
+     - [bugfix] Op. #ELEM(w ana="NOU -PL'): negation for attribute values implemented.
+     - [bugfix] Op. #ELEM(foundry/layer ...): serialization of att=val pairs added.
+     - [bugfix] Op. #ELEM() JUnits added for all theses cases.
+     - [bugfix] Op. #ELEM(): serialization: 'attr' no longer inside 'wrap'.
+
 0.49 2026-05-04
     - [security] Dependencies updated	
     - [cleanup] Silence error listeners in PQ test suite
diff --git a/pom.xml b/pom.xml
index c6e38e1..0dc482b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,10 @@
 			<email>margaretha@ids-mannheim.de</email>
 		</developer>
 		<developer>
+			<name>Franck Bodmer</name>
+			<email>bodmer@ids-mannheim.de</email>
+		</developer>
+		<developer>
 			<name>Elena Irimia</name>
 		</developer>
 	</developers>
@@ -176,6 +180,7 @@
 		          	<exclude>**/c2ps_opBED.java</exclude>
 		          	<exclude>**/c2ps_opELEM.java</exclude>
 		          	<exclude>**/c2ps_opIN.java</exclude>
+		          	<exclude>**/c2ps_opMORPH.java</exclude>
 		          	<exclude>**/c2ps_opOV.java</exclude>
 		          	<exclude>**/c2ps_opPROX.java</exclude>
 		          	<exclude>**/c2ps_opREG.java</exclude>
diff --git a/src/main/antlr/cosmas/c2ps.g b/src/main/antlr/cosmas/c2ps.g
index cbefc94..1fb4c67 100644
--- a/src/main/antlr/cosmas/c2ps.g
+++ b/src/main/antlr/cosmas/c2ps.g
@@ -19,11 +19,21 @@
 //      in &opts&lemma, but wildcards may appear as options inside 'opts'.
 //      E.g. &F+&Prüfung -> lemma with F+ as an option.
 //    - test added for F+.
+//  28.05.26/FB : 
+//    - parsing MORPH() correctly.
+// 	  - rewritePostionQuery(): deactivated because it blindly replaces parts of a query.
+//  11.06.26/FB
+//	  - #BED(Query, positions) reactivated.
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
 grammar c2ps;
 
-options { output=AST; backtrack=true; k=5;}
+// too tolerant: when the parser encounters erronerous characters it skips them and they disappear
+// from the resulting AST without an error message - 10.06.26/FB
+// options { output=AST; backtrack=true; k=5;}
+
+options { output=AST; }
+
 // tokens that will appear as node names in the resulting AST:
 tokens  {C2PQ; OPBED; OPTS; OPBEG; OPEND; OPNHIT; OPALL; OPLEM; OPPROX;
 	 ARG1; ARG2; 
@@ -71,7 +81,7 @@
 //
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
-WS	:	(' '|'\r'|'\n')+ {skip();};
+WS	:	(' '|'\r'|'\n')+ {skip();};  // is executed by the parser, not the lexer - 11.06.26/FB
 
 // Suchoperator PROX:
 // ------------------
@@ -100,6 +110,26 @@
 fragment GROUP
 	:	('min' | 'max');
 
+//OP_MORPH :  'MORPH(' EAVEXPR  ( '&'?  EAVEXPR )*  ')' | 'MORPH(' ')' ;
+
+// must be declared before EAVEXPR
+
+// simply parse to the end of MORPH(...), the content will be parsed
+// by opMORPH.g:
+OP_MORPH 
+	:  'MORPH(' (~(')'|'('))+ ')' | 'MORPH(' ')' ;
+
+// Annotationsoperator #ELEM( EAVEXPR ).
+// EAVEXPR = Element Attribut Value Expression.
+// alle Spezialzeichen vor dem Blank ausgeschlossen.
+// e.g. #ELEM(ANA='N pl'); #ELEM(HEAD, TYPE='DACHUEBERSCHRIFT');
+// e.g. #ELEM( ANA='N()' LEM='L\'été');
+// 29.05.26/FB '&' added for MORPH( a & b ..).
+// 02.06.26/FB must be declared before SEARCHWORD1.
+
+fragment EAVEXPR
+	:	( ~( '&' | '(' | ')' | '\'' | ('\u0000'..'\u001F')) | ('\'' (~('\'' | '\\') | '\\' '\'')* '\'') )+ ;
+	
 // version (12.01.24/FB):
 // accept correct and incorrect chars till the next blank, that way the incorrect chars
 // are submitted to the sub-grammer c2ps_opPROX where they are detected and an appropriate 
@@ -122,6 +152,10 @@
 		  	|
 		  '#REG(' ' '* ~('\''|'"'|' ') (~(')'))* ')';
 
+// OP_ELEM and OP_MORPH must be defined before OP_BED_END - 01.06.26/FB
+// "#ELEM()" nur fuer Fehlerbehandlung, ansonsten sinnlose Anfrage.
+OP_ELEM	 :	'#ELEM(' EAVEXPR ')' | '#ELEM(' ')';
+
 // EAVEXP wird hier eingesetzt für eine beliebige Sequenz von Zeichen bis zu ')'.
 fragment OP_IN_OPTS
 	:	EAVEXPR ;
@@ -130,14 +164,6 @@
 fragment OP_OV_OPTS
 	:	EAVEXPR ;
 
-// OP_BED: #BED( searchExp , Bedingung )
-// OP_BED_END = ", Bedingung )" 
-// ungelöst: #BED(Jimi Hendrix, sa) -> Komma wird "Hendrix," zugeschlagen!
-// Umgehung: Blank vor dem Komma: #BED(Jimi Hendrix , sa) -> OK.
-
-OP_BED_END
-	:	',' ~(')')+ ')' ; 
-	
 // OP1: Operator with single argument:
 // (funktioniert nicht: fragment OP1 : OP1BEG | OP1END ...;)
 
@@ -149,6 +175,23 @@
 SEARCHLABEL
 	:	('<s>' | '<p>' | '<ü>' | '<üd>' | '<üh>' | '<üu>' | '<üz>' | '<ür>');
 
+// OP_BED: #BED( searchExp , Bedingung )
+// OP_BED_END = ", Bedingung )" 
+// ungelöst: #BED(Jimi Hendrix, sa) -> Komma wird "Hendrix," zugeschlagen!
+// Umgehung: Blank vor dem Komma: #BED(Jimi Hendrix , sa) -> OK.
+// OP_BED_END is greedy and must be defined at least after #ELEM() and MORPH(),
+// but also before SEARCHWORD1 (-> still true? - 02.06.26/FB)
+
+// OP_BED_END
+//	:	',' ~(')')+ ')' ; 
+
+fragment OP_BED_PAE	: ('pa'|'pe'|'PA'|'PE');
+fragment OP_BED_SAE	: ('sa'|'se'|'SA'|'SE');
+fragment OP_BED_TAE : ('ta'|'te'|'TA'|'TE');
+
+OP_BED_END
+	: 	' '* ',' ' '* (('+'|'-')?(OP_BED_SAE | OP_BED_PAE | OP_BED_TAE)(','|'/')?)+ ')';
+
 // Search Word: 
 // spezialzeichen werden in "..." versteckt.
 // SEARCHWORD1: single or multiple words not enclosed in "...".
@@ -178,15 +221,6 @@
 SEARCHWORD2
 	:	'"' (~('"') | '\\"')+ '"' ;
 
-// Annotationsoperator #ELEM( EAVEXPR ).
-// EAVEXPR = Element Attribut Value Expression.
-// alle Spezialzeichen vor dem Blank ausgeschlossen.
-// e.g. #ELEM(ANA='N pl'); #ELEM(HEAD, TYPE='DACHUEBERSCHRIFT');
-// e.g. #ELEM( ANA='N()' LEM='L\'été');
-
-fragment EAVEXPR
-	:	( ~( '(' | ')' | '\'' | ('\u0000'..'\u001F')) | ('\'' (~('\'' | '\\') | '\\' '\'')* '\'') )+ ;
-	
 fragment WORD
 	:	~('\t' | ' ' | '/' | '*' | '?' | '+' | '{' | '}' | '[' | ']'
                     | '(' | ')' | '|' | '"' | ',' | ':' | '\'' | '\\' | '!' | '=' | '~' | '&' | '^' | '<' | '>' )+;
@@ -202,9 +236,7 @@
 fragment RE_expr   : (RE_alter | RE_chars)+;
 fragment REGEX       : '"'  (RE_expr | '\'' | ':' )* '"';
 
-// "#ELEM()" nur fuer Fehlerbehandlung, ansonsten sinnlose Anfrage.
-OP_ELEM	:	'#ELEM(' EAVEXPR ')' | '#ELEM(' ')';
-
+/*
 fragment MORPHEXPR
 	: (WORD|REGEX)
 	| WORD ':' (WORD|REGEX)
@@ -213,10 +245,14 @@
 	| WORD '/' WORD '!'? '=' (WORD|REGEX)
 	| WORD '/' WORD '!'? '=' WORD ':' (WORD|REGEX)
 	;
-
+*/
+/*
 OP_MORPH:	'MORPH(' 
 				MORPHEXPR (' '* '&' ' '* MORPHEXPR)* ' '* 
 			')' ;
+*/
+
+// OP_MORPH:	'MORPH(' MORPHEXPR ( '&' MORPHEXPR)*  ')' ;
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 //
@@ -241,9 +277,9 @@
 
 searchExpr1
 	:	op1 			   -> {$op1.tree}
-	| 	searchWord 		   -> {$searchWord.tree}
 	| 	searchLemma 	   -> {$searchLemma.tree}
 	| 	searchAnnot 	   -> {$searchAnnot.tree}
+	| 	searchWord 		   -> {$searchWord.tree}
 	| 	searchLabel        -> {$searchLabel.tree}
 	| 	'(' searchExpr ')' -> {$searchExpr.tree};
 
@@ -265,11 +301,12 @@
 // Suchbegriff = Annotationsoperator:
 // (damit Lexer den richtige Token erzeugt, muss OP_ELEM den gesamten
 // Annot-Ausdruck als 1 Token erkennen).
+
 searchAnnot
 	:	OP_ELEM  
 		-> ^({c2ps_opELEM.check($OP_ELEM.text,$OP_ELEM.index)})
 	| 	OP_MORPH 
-		-> ^(OPMORPH ^({new CommonTree(new CommonToken(OPMORPH, c2ps_opAnnot.strip($OP_MORPH.text)))}));
+		-> ^( {c2ps_opMORPH.check($OP_MORPH.text, $OP_MORPH.index)} );
 
 // searchLabel: <s>, <p>, <ü> etc.
 
diff --git a/src/main/antlr/cosmas/c2ps_opELEM.g b/src/main/antlr/cosmas/c2ps_opELEM.g
index a5b286c..bbde1cc 100644
--- a/src/main/antlr/cosmas/c2ps_opELEM.g
+++ b/src/main/antlr/cosmas/c2ps_opELEM.g
@@ -4,6 +4,8 @@
 // 	lokale Grammatik für #ELEM(Expr).							//
 //	08.01.13/FB										//
 //      v-0.2											//
+// 05.05.26/FB : #ELEM(foundry/layer=name) is treated like #ELEM(avExpr), although it might 
+//   be the element name: OK.
 // TODO: -											//
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
@@ -12,11 +14,12 @@
 options {output=AST;}
 
 tokens {OPELEM; EMPTY;
-	ELNAME; 
-	EQ; NOTEQ;
-	}
+		ELNAME; 
+		EQ; NOTEQ;
+	   }
 @header {package de.ids_mannheim.korap.query.parse.cosmas;}
 @lexer::header {package de.ids_mannheim.korap.query.parse.cosmas;}
+@lexer::members { private boolean inSQuotes = false; private boolean inDQuotes = false; }
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 //
@@ -24,12 +27,25 @@
 //
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
-WS  	:	( ' '|'\t'|'\r'|'\n')+ {$channel=HIDDEN;};
+WS  	:	( ' '|'\t'|'\r'|'\n')+ {skip();}; // old: $channel=HIDDEN;};
 
 // remove '#' from ID to avoid #ELEM(C) being tokenized as an ID;
 // stating '#' should not start an ID has no effect in ANTLR.
 // ID may contain an escaped ', e.g. l\'été.
-ID	   :	(~('#'|'\''|' '|'='|'!'|'<'|'>'|')') | ('\\' '\''))+;
+// ID	   :	(~('#'|'\''|'/'|'\\'| '|'='|'!'|'<'|'>'|')') | ('\\' '\''))+;
+
+// declare ATTID before ID.
+APOS	: '\'' { inSQuotes  = !inSQuotes;};
+APOS2	: '\"' { inDQuotes  = !inDQuotes;};
+ID		: ('A'..'Z'|'a'..'z')('A'..'Z'|'a'..'z'|'0'..'9')*;
+ATTID	: ID '/' ID;
+
+VID		: ( ~( '\''| '\"' | '='|'!'|'<'|'>'|')'|'('|'/'|'\\' | ' ' ) )+ ;
+
+VIDinQ	: {inSQuotes}? => ( (~( '\'' | ' ' ) | ('\\' '\'') )+ )
+		| {inDQuotes}? => ( (~( '\"' | ' ' ) | ('\\' '\"') )+ );
+
+OPEQ	: {!inSQuotes && !inDQuotes}? '=';
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 //
@@ -37,16 +53,25 @@
 //
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
-opELEM	:	'#ELEM(' ')'              -> ^(OPELEM EMPTY)
-	|	'#ELEM(' elem avExpr* ')' -> ^(OPELEM elem avExpr*)
-	|	'#ELEM(' avExpr+ ')'      -> ^(OPELEM avExpr+);
+opELEM	:	'#ELEM(' ')'              EOF -> ^(OPELEM EMPTY)
+		|	'#ELEM(' elem avExpr+ ')' EOF -> ^(OPELEM elem avExpr+)
+		|	'#ELEM(' elem ')' 		  EOF -> ^(OPELEM elem)
+		|	'#ELEM(' avExpr+ ')'      EOF -> ^(OPELEM avExpr+);
 
-elem	:	ID -> ^(ELNAME ID);
+// elem = element name:
+elem	:	(ID) => ID -> ^(ELNAME ID);
 
-avExpr	:	id1=ID op id2=ID            -> ^(op $id1 $id2)
-	|	id1=ID op '\'' id3+=ID+ '\'' -> ^(op $id1 $id3+);
+att		: 	ATTID 		 
+		|	ID;
+		
+val0	: 	ID  
+		|	VID ;
+
+vals	:   (VIDinQ | ID)+;  
+
+avExpr	:	att op APOS  vals APOS  -> ^(op att vals)
+		|	att op APOS2 vals APOS2 -> ^(op att vals)
+		|	att op val0			    -> ^(op att val0) ;
 	
-op	:	'='             -> ^(EQ)
-	|	('<>' | '!=')   -> ^(NOTEQ);
-	
-	
+op		:	OPEQ			-> ^(EQ)
+		|	('<>' | '!=')   -> ^(NOTEQ);
diff --git a/src/main/antlr/cosmas/c2ps_opMORPH.g b/src/main/antlr/cosmas/c2ps_opMORPH.g
new file mode 100644
index 0000000..bc4127c
--- /dev/null
+++ b/src/main/antlr/cosmas/c2ps_opMORPH.g
@@ -0,0 +1,68 @@
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+//																				//
+// 	COSMAS II zeilenorientierten Suchanfragesprache (C2 plain syntax)			//
+// 	lokale Grammatik für MORPH(Expr).											//
+//	01.06.26/FB																	//
+// TODO: -																		//
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+grammar c2ps_opMORPH;
+
+options {output=AST;}
+
+tokens {OPMORPH; EMPTY;
+	   }
+		
+@header {package de.ids_mannheim.korap.query.parse.cosmas;}
+@lexer::header {package de.ids_mannheim.korap.query.parse.cosmas;}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+//
+// 						Lexer
+//
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+WS  	:	( ' '|'\t'|'\r'|'\n')+ {skip();}; // $channel=HIDDEN;};
+
+// remove '#' from ID to avoid #ELEM(C) being tokenized as an ID;
+// starjating '#' should not start an ID has no effect in ANTLR.
+// ID may contain an escaped ', e.g. l\'été.
+//ID	   :	(~('#'|'\''|' '|'='|'!'|'<'|'>'|')') | ('\\' '\''))+;
+
+// accept 1..3 for person annotation in marmot:
+
+EXPR		: ('a'..'z' | 'A'..'Z')('a'..'z' | 'A'..'Z'| '0'..'9'|'_')* | ( '1'|'2'|'3');
+REG_EXPR	: ('a'..'z' | 'A'..'Z')('a'..'z' | 'A'..'Z'| '0'..'9'|'_' | '[' | ']' | '*' | '?' | '+' | '.' | '\\')* ;
+
+OP		: ('=' | '!=' | '<>' ) ;
+
+// Yes, ' ' must be specified as being part of the token that the lexer is identifying,
+// i.e. when tokenizing the input, the WS rule is not activ.
+// n.b. AVEXP1-6 must return the whole expression as 1 token with blanks in lists = 1 avExpr.
+
+fragment B_EXPR	: EXPR ':' '-'? REG_EXPR | '-'? REG_EXPR ;
+
+AVEXPR1	: EXPR '/' EXPR OP (B_EXPR | '\'' B_EXPR (' ' | B_EXPR)* '\'' | '"' B_EXPR (' ' | B_EXPR)* '"' ) ;
+AVEXPR3	:          EXPR OP (B_EXPR | '\'' B_EXPR (' ' | B_EXPR)* '\'' | '"' B_EXPR (' ' | B_EXPR)* '"' ) ;
+AVEXPR5	:                  EXPR ':' '-'? REG_EXPR ;
+AVEXPR6 :							'-'? REG_EXPR ;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+//
+// 						Parser
+//
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+// the '&' is optional and removed from the resulting AST.
+
+opMORPH	:	'MORPH(' ')'		 				EOF	-> ^(OPMORPH EMPTY)
+		|	'MORPH(' avExpr ('&'? avExpr)* ')' 	EOF	-> ^(OPMORPH avExpr+ );
+		
+avExpr	: AVEXPR1 
+		| AVEXPR3
+		| AVEXPR5
+		| AVEXPR6
+		| EXPR
+		| REG_EXPR;
+
+
diff --git a/src/main/antlr/cosmas/c2ps_opWF.g b/src/main/antlr/cosmas/c2ps_opWF.g
index 8e3462e..50e7bd5 100644
--- a/src/main/antlr/cosmas/c2ps_opWF.g
+++ b/src/main/antlr/cosmas/c2ps_opWF.g
@@ -20,7 +20,8 @@
 
 options {output=AST;}
 
-tokens  { OPWF; OPLEM; OPTCASE; TPOS; }
+//tokens  { OPWF; OPLEM; OPTCASE; TPOS; }
+tokens  { OPWF; OPLEM; OPTCASE; TPOS; OPTS; OPBED; }
 @header {package de.ids_mannheim.korap.query.parse.cosmas;}
 @lexer::header {package de.ids_mannheim.korap.query.parse.cosmas;}
 
@@ -49,10 +50,19 @@
  
 searchWFs[int pos]
 	:	searchWF[pos]+;
-	
+/*
 searchWF[int pos] :	optCase? wordform[OPWF,pos] tpos?
 
 		-> ^(OPWF wordform optCase? tpos? ) ;
+*/
+
+// insert OPWF into OPBEG if TPOS is present - 11.06.26/FB
+
+searchWF[int pos] 
+		:	optCase? wordform[OPWF,pos] tpos
+		-> ^(OPBED ^(OPWF wordform optCase?) tpos )
+		|	optCase? wordform[OPWF,pos]
+		-> ^(OPWF wordform optCase? ) ;
 
 wordform[int type, int pos]:	WF -> {c2ps_opWF.encode($WF.text, $type, $pos)};
 
@@ -64,7 +74,8 @@
 // textposition Options:
 tpos	:	TPos 
 
-		-> ^(TPOS {c2ps_opBED.checkTPos($TPos.text, $TPos.index)});
+		// -> ^(TPOS {c2ps_opBED.checkTPos($TPos.text, $TPos.index)});
+		-> ^(OPTS {c2ps_opBED.checkTPos($TPos.text, $TPos.index)});
 
 // analog für Lemmata, kein optCase:
 // todo: check wordform (=lemma) for wildcards, which are not allowed in the lemma expr.
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opELEM.java b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opELEM.java
index 129ba49..d618331 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opELEM.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opELEM.java
@@ -2,13 +2,16 @@
 
 import org.antlr.runtime.*;
 import org.antlr.runtime.tree.*;
+import de.ids_mannheim.korap.util.*;
 
 // parses Search Expression inside #ELEM(...):
 
 public class c2ps_opELEM
 
 {
-
+	final static boolean bShow  	 = false;
+	final static boolean bShowTokens = false;
+	
     /* Method check():
      * input: e.g. #ELEM(S), #ELEM(W ANA='DET ADJ'),
      *             #ELEM(ANA <> 'V sg' TYP !=VP), etc.
@@ -20,10 +23,13 @@
         c2ps_opELEMParser g = new c2ps_opELEMParser(tokens);
         c2ps_opELEMParser.opELEM_return c2PQReturn = null;
 
-        /*
-        System.out.println("check opELEM: " + index + ": " + "'" + input + "'");
-        System.out.flush();
-         */
+        if( bShow )
+        	{
+        	System.out.println("check opELEM: index=" + index + ", input='" + input + "'");
+        	System.out.flush();
+        	if( bShowTokens )
+        		TokenUtils.printLexerTokens(tokens, "opELEM");
+        	}
 
         try {
             c2PQReturn = g.opELEM();
@@ -34,7 +40,9 @@
 
         // AST Tree anzeigen:
         Tree tree = (Tree) c2PQReturn.getTree();
-        //System.out.println("#ELEM Opts: " + tree.toStringTree() );
+        
+        if( bShow )
+        	System.out.println("check opELEM: #ELEM Opts: " + tree.toStringTree() );
 
         return tree;
     }
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.java b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.java
new file mode 100644
index 0000000..5155c74
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.java
@@ -0,0 +1,82 @@
+package de.ids_mannheim.korap.query.parse.cosmas;
+
+import org.antlr.runtime.*;
+import org.antlr.runtime.tree.*;
+import de.ids_mannheim.korap.util.*;
+
+/* parses Search Expression inside MORPH(...).
+ * 01.06.26/FB
+ */
+
+
+public class c2ps_opMORPH
+
+{
+	final static boolean bShow  	 = false;
+	final static boolean bShowTokens = false;
+	
+	/* Method check():
+     * input: 
+     *  - MORPH(), 
+     * 	- MORPH(NOU SG EIGN),
+     *  - MORPH(foundry/layer=NOU), MORPH(foundry/layer=temp:past) etc.
+     *  - MORPH(foundry1/layer1=NOU (&)? foundry2/layer2/temp:pres etc. ).
+     * 
+     */
+    public static Tree check (String input, int index) 
+    {
+        ANTLRStringStream ss 		= new ANTLRStringStream(input);
+        c2ps_opMORPHLexer lex 		= new c2ps_opMORPHLexer(ss);
+        CommonTokenStream tokens 	= new CommonTokenStream(lex);
+        c2ps_opMORPHParser g 		= new c2ps_opMORPHParser(tokens);
+        c2ps_opMORPHParser.opMORPH_return 
+        	c2PQReturn 				= null;
+
+        if( bShow )
+        	{
+        	System.out.printf("check opMORPH index=%d, input='%s'.\n", index, input); 
+        	if( bShowTokens )
+        		TokenUtils.printLexerTokens(tokens, "opMORPH");
+        	}
+
+        try {
+            c2PQReturn = g.opMORPH();
+        	}
+        catch (RecognitionException e) {
+            e.printStackTrace();
+        	}
+       
+        Tree tree = (Tree) c2PQReturn.getTree();
+        
+        // show AST Tree:
+        if( bShow )
+        	System.out.printf("check opMORPH: AST='%s'.\n", tree.toStringTree());
+        	
+        return tree;
+    }
+
+
+    /*
+     * main - Testprogramm for #ELEM(...)
+     */
+
+    public static void main (String args[]) throws Exception 
+    {
+    /*
+         String[] input = { "#ELEM()", "#ELEM(   )", "#ELEM(S)",
+     
+                "#ELEM(W ANA='DET ADV')",
+                "#ELEM( TITLE TYPE!=Unterüberschrift )",
+                "#ELEM(v='a b c' w!='d e f' x=y )",
+                "#ELEM(flexion='l\\'été' lemma='été')" };
+        Tree tree;
+
+        for (int i = 0; i < input.length; i++) {
+            System.out.println("#ELEM input: " + input[i]);
+            tree = check(input[i], 0);
+            System.out.println("#ELEM AST  : " + tree.toStringTree());
+        }
+	*/
+    } // main
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.tokens b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.tokens
new file mode 100644
index 0000000..fb0280d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPH.tokens
@@ -0,0 +1,17 @@
+T__15=15
+T__16=16
+T__17=17
+AVEXPR1=4
+AVEXPR3=5
+AVEXPR5=6
+AVEXPR6=7
+B_EXPR=8
+EMPTY=9
+EXPR=10
+OP=11
+OPMORPH=12
+REG_EXPR=13
+WS=14
+'&'=15
+')'=16
+'MORPH('=17
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHLexer.java b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHLexer.java
new file mode 100644
index 0000000..40918eb
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHLexer.java
@@ -0,0 +1,1128 @@
+// $ANTLR 3.5.3 c2ps_opMORPH.g 2026-07-28 12:36:06
+package de.ids_mannheim.korap.query.parse.cosmas;
+
+import org.antlr.runtime.*;
+import java.util.Stack;
+import java.util.List;
+import java.util.ArrayList;
+
+@SuppressWarnings("all")
+public class c2ps_opMORPHLexer extends Lexer {
+	public static final int EOF=-1;
+	public static final int T__15=15;
+	public static final int T__16=16;
+	public static final int T__17=17;
+	public static final int AVEXPR1=4;
+	public static final int AVEXPR3=5;
+	public static final int AVEXPR5=6;
+	public static final int AVEXPR6=7;
+	public static final int B_EXPR=8;
+	public static final int EMPTY=9;
+	public static final int EXPR=10;
+	public static final int OP=11;
+	public static final int OPMORPH=12;
+	public static final int REG_EXPR=13;
+	public static final int WS=14;
+
+	// delegates
+	// delegators
+	public Lexer[] getDelegates() {
+		return new Lexer[] {};
+	}
+
+	public c2ps_opMORPHLexer() {} 
+	public c2ps_opMORPHLexer(CharStream input) {
+		this(input, new RecognizerSharedState());
+	}
+	public c2ps_opMORPHLexer(CharStream input, RecognizerSharedState state) {
+		super(input,state);
+	}
+	@Override public String getGrammarFileName() { return "c2ps_opMORPH.g"; }
+
+	// $ANTLR start "T__15"
+	public final void mT__15() throws RecognitionException {
+		try {
+			int _type = T__15;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:4:7: ( '&' )
+			// c2ps_opMORPH.g:4:9: '&'
+			{
+			match('&'); 
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "T__15"
+
+	// $ANTLR start "T__16"
+	public final void mT__16() throws RecognitionException {
+		try {
+			int _type = T__16;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:5:7: ( ')' )
+			// c2ps_opMORPH.g:5:9: ')'
+			{
+			match(')'); 
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "T__16"
+
+	// $ANTLR start "T__17"
+	public final void mT__17() throws RecognitionException {
+		try {
+			int _type = T__17;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:6:7: ( 'MORPH(' )
+			// c2ps_opMORPH.g:6:9: 'MORPH('
+			{
+			match("MORPH("); 
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "T__17"
+
+	// $ANTLR start "WS"
+	public final void mWS() throws RecognitionException {
+		try {
+			int _type = WS;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:25:6: ( ( ' ' | '\\t' | '\\r' | '\\n' )+ )
+			// c2ps_opMORPH.g:25:8: ( ' ' | '\\t' | '\\r' | '\\n' )+
+			{
+			// c2ps_opMORPH.g:25:8: ( ' ' | '\\t' | '\\r' | '\\n' )+
+			int cnt1=0;
+			loop1:
+			while (true) {
+				int alt1=2;
+				int LA1_0 = input.LA(1);
+				if ( ((LA1_0 >= '\t' && LA1_0 <= '\n')||LA1_0=='\r'||LA1_0==' ') ) {
+					alt1=1;
+				}
+
+				switch (alt1) {
+				case 1 :
+					// c2ps_opMORPH.g:
+					{
+					if ( (input.LA(1) >= '\t' && input.LA(1) <= '\n')||input.LA(1)=='\r'||input.LA(1)==' ' ) {
+						input.consume();
+					}
+					else {
+						MismatchedSetException mse = new MismatchedSetException(null,input);
+						recover(mse);
+						throw mse;
+					}
+					}
+					break;
+
+				default :
+					if ( cnt1 >= 1 ) break loop1;
+					EarlyExitException eee = new EarlyExitException(1, input);
+					throw eee;
+				}
+				cnt1++;
+			}
+
+			skip();
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "WS"
+
+	// $ANTLR start "EXPR"
+	public final void mEXPR() throws RecognitionException {
+		try {
+			int _type = EXPR;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:34:7: ( ( 'a' .. 'z' | 'A' .. 'Z' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )* | ( '1' | '2' | '3' ) )
+			int alt3=2;
+			int LA3_0 = input.LA(1);
+			if ( ((LA3_0 >= 'A' && LA3_0 <= 'Z')||(LA3_0 >= 'a' && LA3_0 <= 'z')) ) {
+				alt3=1;
+			}
+			else if ( ((LA3_0 >= '1' && LA3_0 <= '3')) ) {
+				alt3=2;
+			}
+
+			else {
+				NoViableAltException nvae =
+					new NoViableAltException("", 3, 0, input);
+				throw nvae;
+			}
+
+			switch (alt3) {
+				case 1 :
+					// c2ps_opMORPH.g:34:9: ( 'a' .. 'z' | 'A' .. 'Z' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )*
+					{
+					if ( (input.LA(1) >= 'A' && input.LA(1) <= 'Z')||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+						input.consume();
+					}
+					else {
+						MismatchedSetException mse = new MismatchedSetException(null,input);
+						recover(mse);
+						throw mse;
+					}
+					// c2ps_opMORPH.g:34:30: ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )*
+					loop2:
+					while (true) {
+						int alt2=2;
+						int LA2_0 = input.LA(1);
+						if ( ((LA2_0 >= '0' && LA2_0 <= '9')||(LA2_0 >= 'A' && LA2_0 <= 'Z')||LA2_0=='_'||(LA2_0 >= 'a' && LA2_0 <= 'z')) ) {
+							alt2=1;
+						}
+
+						switch (alt2) {
+						case 1 :
+							// c2ps_opMORPH.g:
+							{
+							if ( (input.LA(1) >= '0' && input.LA(1) <= '9')||(input.LA(1) >= 'A' && input.LA(1) <= 'Z')||input.LA(1)=='_'||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+								input.consume();
+							}
+							else {
+								MismatchedSetException mse = new MismatchedSetException(null,input);
+								recover(mse);
+								throw mse;
+							}
+							}
+							break;
+
+						default :
+							break loop2;
+						}
+					}
+
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:34:69: ( '1' | '2' | '3' )
+					{
+					if ( (input.LA(1) >= '1' && input.LA(1) <= '3') ) {
+						input.consume();
+					}
+					else {
+						MismatchedSetException mse = new MismatchedSetException(null,input);
+						recover(mse);
+						throw mse;
+					}
+					}
+					break;
+
+			}
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "EXPR"
+
+	// $ANTLR start "REG_EXPR"
+	public final void mREG_EXPR() throws RecognitionException {
+		try {
+			int _type = REG_EXPR;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:35:10: ( ( 'a' .. 'z' | 'A' .. 'Z' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '[' | ']' | '*' | '?' | '+' | '.' | '\\\\' )* )
+			// c2ps_opMORPH.g:35:12: ( 'a' .. 'z' | 'A' .. 'Z' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '[' | ']' | '*' | '?' | '+' | '.' | '\\\\' )*
+			{
+			if ( (input.LA(1) >= 'A' && input.LA(1) <= 'Z')||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+				input.consume();
+			}
+			else {
+				MismatchedSetException mse = new MismatchedSetException(null,input);
+				recover(mse);
+				throw mse;
+			}
+			// c2ps_opMORPH.g:35:33: ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '[' | ']' | '*' | '?' | '+' | '.' | '\\\\' )*
+			loop4:
+			while (true) {
+				int alt4=2;
+				int LA4_0 = input.LA(1);
+				if ( ((LA4_0 >= '*' && LA4_0 <= '+')||LA4_0=='.'||(LA4_0 >= '0' && LA4_0 <= '9')||LA4_0=='?'||(LA4_0 >= 'A' && LA4_0 <= ']')||LA4_0=='_'||(LA4_0 >= 'a' && LA4_0 <= 'z')) ) {
+					alt4=1;
+				}
+
+				switch (alt4) {
+				case 1 :
+					// c2ps_opMORPH.g:
+					{
+					if ( (input.LA(1) >= '*' && input.LA(1) <= '+')||input.LA(1)=='.'||(input.LA(1) >= '0' && input.LA(1) <= '9')||input.LA(1)=='?'||(input.LA(1) >= 'A' && input.LA(1) <= ']')||input.LA(1)=='_'||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+						input.consume();
+					}
+					else {
+						MismatchedSetException mse = new MismatchedSetException(null,input);
+						recover(mse);
+						throw mse;
+					}
+					}
+					break;
+
+				default :
+					break loop4;
+				}
+			}
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "REG_EXPR"
+
+	// $ANTLR start "OP"
+	public final void mOP() throws RecognitionException {
+		try {
+			int _type = OP;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:37:5: ( ( '=' | '!=' | '<>' ) )
+			// c2ps_opMORPH.g:37:7: ( '=' | '!=' | '<>' )
+			{
+			// c2ps_opMORPH.g:37:7: ( '=' | '!=' | '<>' )
+			int alt5=3;
+			switch ( input.LA(1) ) {
+			case '=':
+				{
+				alt5=1;
+				}
+				break;
+			case '!':
+				{
+				alt5=2;
+				}
+				break;
+			case '<':
+				{
+				alt5=3;
+				}
+				break;
+			default:
+				NoViableAltException nvae =
+					new NoViableAltException("", 5, 0, input);
+				throw nvae;
+			}
+			switch (alt5) {
+				case 1 :
+					// c2ps_opMORPH.g:37:8: '='
+					{
+					match('='); 
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:37:14: '!='
+					{
+					match("!="); 
+
+					}
+					break;
+				case 3 :
+					// c2ps_opMORPH.g:37:21: '<>'
+					{
+					match("<>"); 
+
+					}
+					break;
+
+			}
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "OP"
+
+	// $ANTLR start "B_EXPR"
+	public final void mB_EXPR() throws RecognitionException {
+		try {
+			// c2ps_opMORPH.g:43:17: ( EXPR ':' ( '-' )? REG_EXPR | ( '-' )? REG_EXPR )
+			int alt8=2;
+			alt8 = dfa8.predict(input);
+			switch (alt8) {
+				case 1 :
+					// c2ps_opMORPH.g:43:19: EXPR ':' ( '-' )? REG_EXPR
+					{
+					mEXPR(); 
+
+					match(':'); 
+					// c2ps_opMORPH.g:43:28: ( '-' )?
+					int alt6=2;
+					int LA6_0 = input.LA(1);
+					if ( (LA6_0=='-') ) {
+						alt6=1;
+					}
+					switch (alt6) {
+						case 1 :
+							// c2ps_opMORPH.g:43:28: '-'
+							{
+							match('-'); 
+							}
+							break;
+
+					}
+
+					mREG_EXPR(); 
+
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:43:44: ( '-' )? REG_EXPR
+					{
+					// c2ps_opMORPH.g:43:44: ( '-' )?
+					int alt7=2;
+					int LA7_0 = input.LA(1);
+					if ( (LA7_0=='-') ) {
+						alt7=1;
+					}
+					switch (alt7) {
+						case 1 :
+							// c2ps_opMORPH.g:43:44: '-'
+							{
+							match('-'); 
+							}
+							break;
+
+					}
+
+					mREG_EXPR(); 
+
+					}
+					break;
+
+			}
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "B_EXPR"
+
+	// $ANTLR start "AVEXPR1"
+	public final void mAVEXPR1() throws RecognitionException {
+		try {
+			int _type = AVEXPR1;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:45:9: ( EXPR '/' EXPR OP ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' ) )
+			// c2ps_opMORPH.g:45:11: EXPR '/' EXPR OP ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' )
+			{
+			mEXPR(); 
+
+			match('/'); 
+			mEXPR(); 
+
+			mOP(); 
+
+			// c2ps_opMORPH.g:45:28: ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' )
+			int alt11=3;
+			switch ( input.LA(1) ) {
+			case '-':
+			case '1':
+			case '2':
+			case '3':
+			case 'A':
+			case 'B':
+			case 'C':
+			case 'D':
+			case 'E':
+			case 'F':
+			case 'G':
+			case 'H':
+			case 'I':
+			case 'J':
+			case 'K':
+			case 'L':
+			case 'M':
+			case 'N':
+			case 'O':
+			case 'P':
+			case 'Q':
+			case 'R':
+			case 'S':
+			case 'T':
+			case 'U':
+			case 'V':
+			case 'W':
+			case 'X':
+			case 'Y':
+			case 'Z':
+			case 'a':
+			case 'b':
+			case 'c':
+			case 'd':
+			case 'e':
+			case 'f':
+			case 'g':
+			case 'h':
+			case 'i':
+			case 'j':
+			case 'k':
+			case 'l':
+			case 'm':
+			case 'n':
+			case 'o':
+			case 'p':
+			case 'q':
+			case 'r':
+			case 's':
+			case 't':
+			case 'u':
+			case 'v':
+			case 'w':
+			case 'x':
+			case 'y':
+			case 'z':
+				{
+				alt11=1;
+				}
+				break;
+			case '\'':
+				{
+				alt11=2;
+				}
+				break;
+			case '\"':
+				{
+				alt11=3;
+				}
+				break;
+			default:
+				NoViableAltException nvae =
+					new NoViableAltException("", 11, 0, input);
+				throw nvae;
+			}
+			switch (alt11) {
+				case 1 :
+					// c2ps_opMORPH.g:45:29: B_EXPR
+					{
+					mB_EXPR(); 
+
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:45:38: '\\'' B_EXPR ( ' ' | B_EXPR )* '\\''
+					{
+					match('\''); 
+					mB_EXPR(); 
+
+					// c2ps_opMORPH.g:45:50: ( ' ' | B_EXPR )*
+					loop9:
+					while (true) {
+						int alt9=3;
+						int LA9_0 = input.LA(1);
+						if ( (LA9_0==' ') ) {
+							alt9=1;
+						}
+						else if ( (LA9_0=='-'||(LA9_0 >= '1' && LA9_0 <= '3')||(LA9_0 >= 'A' && LA9_0 <= 'Z')||(LA9_0 >= 'a' && LA9_0 <= 'z')) ) {
+							alt9=2;
+						}
+
+						switch (alt9) {
+						case 1 :
+							// c2ps_opMORPH.g:45:51: ' '
+							{
+							match(' '); 
+							}
+							break;
+						case 2 :
+							// c2ps_opMORPH.g:45:57: B_EXPR
+							{
+							mB_EXPR(); 
+
+							}
+							break;
+
+						default :
+							break loop9;
+						}
+					}
+
+					match('\''); 
+					}
+					break;
+				case 3 :
+					// c2ps_opMORPH.g:45:73: '\"' B_EXPR ( ' ' | B_EXPR )* '\"'
+					{
+					match('\"'); 
+					mB_EXPR(); 
+
+					// c2ps_opMORPH.g:45:84: ( ' ' | B_EXPR )*
+					loop10:
+					while (true) {
+						int alt10=3;
+						int LA10_0 = input.LA(1);
+						if ( (LA10_0==' ') ) {
+							alt10=1;
+						}
+						else if ( (LA10_0=='-'||(LA10_0 >= '1' && LA10_0 <= '3')||(LA10_0 >= 'A' && LA10_0 <= 'Z')||(LA10_0 >= 'a' && LA10_0 <= 'z')) ) {
+							alt10=2;
+						}
+
+						switch (alt10) {
+						case 1 :
+							// c2ps_opMORPH.g:45:85: ' '
+							{
+							match(' '); 
+							}
+							break;
+						case 2 :
+							// c2ps_opMORPH.g:45:91: B_EXPR
+							{
+							mB_EXPR(); 
+
+							}
+							break;
+
+						default :
+							break loop10;
+						}
+					}
+
+					match('\"'); 
+					}
+					break;
+
+			}
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "AVEXPR1"
+
+	// $ANTLR start "AVEXPR3"
+	public final void mAVEXPR3() throws RecognitionException {
+		try {
+			int _type = AVEXPR3;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:46:9: ( EXPR OP ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' ) )
+			// c2ps_opMORPH.g:46:20: EXPR OP ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' )
+			{
+			mEXPR(); 
+
+			mOP(); 
+
+			// c2ps_opMORPH.g:46:28: ( B_EXPR | '\\'' B_EXPR ( ' ' | B_EXPR )* '\\'' | '\"' B_EXPR ( ' ' | B_EXPR )* '\"' )
+			int alt14=3;
+			switch ( input.LA(1) ) {
+			case '-':
+			case '1':
+			case '2':
+			case '3':
+			case 'A':
+			case 'B':
+			case 'C':
+			case 'D':
+			case 'E':
+			case 'F':
+			case 'G':
+			case 'H':
+			case 'I':
+			case 'J':
+			case 'K':
+			case 'L':
+			case 'M':
+			case 'N':
+			case 'O':
+			case 'P':
+			case 'Q':
+			case 'R':
+			case 'S':
+			case 'T':
+			case 'U':
+			case 'V':
+			case 'W':
+			case 'X':
+			case 'Y':
+			case 'Z':
+			case 'a':
+			case 'b':
+			case 'c':
+			case 'd':
+			case 'e':
+			case 'f':
+			case 'g':
+			case 'h':
+			case 'i':
+			case 'j':
+			case 'k':
+			case 'l':
+			case 'm':
+			case 'n':
+			case 'o':
+			case 'p':
+			case 'q':
+			case 'r':
+			case 's':
+			case 't':
+			case 'u':
+			case 'v':
+			case 'w':
+			case 'x':
+			case 'y':
+			case 'z':
+				{
+				alt14=1;
+				}
+				break;
+			case '\'':
+				{
+				alt14=2;
+				}
+				break;
+			case '\"':
+				{
+				alt14=3;
+				}
+				break;
+			default:
+				NoViableAltException nvae =
+					new NoViableAltException("", 14, 0, input);
+				throw nvae;
+			}
+			switch (alt14) {
+				case 1 :
+					// c2ps_opMORPH.g:46:29: B_EXPR
+					{
+					mB_EXPR(); 
+
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:46:38: '\\'' B_EXPR ( ' ' | B_EXPR )* '\\''
+					{
+					match('\''); 
+					mB_EXPR(); 
+
+					// c2ps_opMORPH.g:46:50: ( ' ' | B_EXPR )*
+					loop12:
+					while (true) {
+						int alt12=3;
+						int LA12_0 = input.LA(1);
+						if ( (LA12_0==' ') ) {
+							alt12=1;
+						}
+						else if ( (LA12_0=='-'||(LA12_0 >= '1' && LA12_0 <= '3')||(LA12_0 >= 'A' && LA12_0 <= 'Z')||(LA12_0 >= 'a' && LA12_0 <= 'z')) ) {
+							alt12=2;
+						}
+
+						switch (alt12) {
+						case 1 :
+							// c2ps_opMORPH.g:46:51: ' '
+							{
+							match(' '); 
+							}
+							break;
+						case 2 :
+							// c2ps_opMORPH.g:46:57: B_EXPR
+							{
+							mB_EXPR(); 
+
+							}
+							break;
+
+						default :
+							break loop12;
+						}
+					}
+
+					match('\''); 
+					}
+					break;
+				case 3 :
+					// c2ps_opMORPH.g:46:73: '\"' B_EXPR ( ' ' | B_EXPR )* '\"'
+					{
+					match('\"'); 
+					mB_EXPR(); 
+
+					// c2ps_opMORPH.g:46:84: ( ' ' | B_EXPR )*
+					loop13:
+					while (true) {
+						int alt13=3;
+						int LA13_0 = input.LA(1);
+						if ( (LA13_0==' ') ) {
+							alt13=1;
+						}
+						else if ( (LA13_0=='-'||(LA13_0 >= '1' && LA13_0 <= '3')||(LA13_0 >= 'A' && LA13_0 <= 'Z')||(LA13_0 >= 'a' && LA13_0 <= 'z')) ) {
+							alt13=2;
+						}
+
+						switch (alt13) {
+						case 1 :
+							// c2ps_opMORPH.g:46:85: ' '
+							{
+							match(' '); 
+							}
+							break;
+						case 2 :
+							// c2ps_opMORPH.g:46:91: B_EXPR
+							{
+							mB_EXPR(); 
+
+							}
+							break;
+
+						default :
+							break loop13;
+						}
+					}
+
+					match('\"'); 
+					}
+					break;
+
+			}
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "AVEXPR3"
+
+	// $ANTLR start "AVEXPR5"
+	public final void mAVEXPR5() throws RecognitionException {
+		try {
+			int _type = AVEXPR5;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:47:9: ( EXPR ':' ( '-' )? REG_EXPR )
+			// c2ps_opMORPH.g:47:28: EXPR ':' ( '-' )? REG_EXPR
+			{
+			mEXPR(); 
+
+			match(':'); 
+			// c2ps_opMORPH.g:47:37: ( '-' )?
+			int alt15=2;
+			int LA15_0 = input.LA(1);
+			if ( (LA15_0=='-') ) {
+				alt15=1;
+			}
+			switch (alt15) {
+				case 1 :
+					// c2ps_opMORPH.g:47:37: '-'
+					{
+					match('-'); 
+					}
+					break;
+
+			}
+
+			mREG_EXPR(); 
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "AVEXPR5"
+
+	// $ANTLR start "AVEXPR6"
+	public final void mAVEXPR6() throws RecognitionException {
+		try {
+			int _type = AVEXPR6;
+			int _channel = DEFAULT_TOKEN_CHANNEL;
+			// c2ps_opMORPH.g:48:9: ( ( '-' )? REG_EXPR )
+			// c2ps_opMORPH.g:48:17: ( '-' )? REG_EXPR
+			{
+			// c2ps_opMORPH.g:48:17: ( '-' )?
+			int alt16=2;
+			int LA16_0 = input.LA(1);
+			if ( (LA16_0=='-') ) {
+				alt16=1;
+			}
+			switch (alt16) {
+				case 1 :
+					// c2ps_opMORPH.g:48:17: '-'
+					{
+					match('-'); 
+					}
+					break;
+
+			}
+
+			mREG_EXPR(); 
+
+			}
+
+			state.type = _type;
+			state.channel = _channel;
+		}
+		finally {
+			// do for sure before leaving
+		}
+	}
+	// $ANTLR end "AVEXPR6"
+
+	@Override
+	public void mTokens() throws RecognitionException {
+		// c2ps_opMORPH.g:1:8: ( T__15 | T__16 | T__17 | WS | EXPR | REG_EXPR | OP | AVEXPR1 | AVEXPR3 | AVEXPR5 | AVEXPR6 )
+		int alt17=11;
+		alt17 = dfa17.predict(input);
+		switch (alt17) {
+			case 1 :
+				// c2ps_opMORPH.g:1:10: T__15
+				{
+				mT__15(); 
+
+				}
+				break;
+			case 2 :
+				// c2ps_opMORPH.g:1:16: T__16
+				{
+				mT__16(); 
+
+				}
+				break;
+			case 3 :
+				// c2ps_opMORPH.g:1:22: T__17
+				{
+				mT__17(); 
+
+				}
+				break;
+			case 4 :
+				// c2ps_opMORPH.g:1:28: WS
+				{
+				mWS(); 
+
+				}
+				break;
+			case 5 :
+				// c2ps_opMORPH.g:1:31: EXPR
+				{
+				mEXPR(); 
+
+				}
+				break;
+			case 6 :
+				// c2ps_opMORPH.g:1:36: REG_EXPR
+				{
+				mREG_EXPR(); 
+
+				}
+				break;
+			case 7 :
+				// c2ps_opMORPH.g:1:45: OP
+				{
+				mOP(); 
+
+				}
+				break;
+			case 8 :
+				// c2ps_opMORPH.g:1:48: AVEXPR1
+				{
+				mAVEXPR1(); 
+
+				}
+				break;
+			case 9 :
+				// c2ps_opMORPH.g:1:56: AVEXPR3
+				{
+				mAVEXPR3(); 
+
+				}
+				break;
+			case 10 :
+				// c2ps_opMORPH.g:1:64: AVEXPR5
+				{
+				mAVEXPR5(); 
+
+				}
+				break;
+			case 11 :
+				// c2ps_opMORPH.g:1:72: AVEXPR6
+				{
+				mAVEXPR6(); 
+
+				}
+				break;
+
+		}
+	}
+
+
+	protected DFA8 dfa8 = new DFA8(this);
+	protected DFA17 dfa17 = new DFA17(this);
+	static final String DFA8_eotS =
+		"\1\uffff\1\3\2\uffff\1\3";
+	static final String DFA8_eofS =
+		"\5\uffff";
+	static final String DFA8_minS =
+		"\1\55\1\60\2\uffff\1\60";
+	static final String DFA8_maxS =
+		"\2\172\2\uffff\1\172";
+	static final String DFA8_acceptS =
+		"\2\uffff\1\1\1\2\1\uffff";
+	static final String DFA8_specialS =
+		"\5\uffff}>";
+	static final String[] DFA8_transitionS = {
+			"\1\3\3\uffff\3\2\15\uffff\32\1\6\uffff\32\1",
+			"\12\4\1\2\6\uffff\32\4\4\uffff\1\4\1\uffff\32\4",
+			"",
+			"",
+			"\12\4\1\2\6\uffff\32\4\4\uffff\1\4\1\uffff\32\4"
+	};
+
+	static final short[] DFA8_eot = DFA.unpackEncodedString(DFA8_eotS);
+	static final short[] DFA8_eof = DFA.unpackEncodedString(DFA8_eofS);
+	static final char[] DFA8_min = DFA.unpackEncodedStringToUnsignedChars(DFA8_minS);
+	static final char[] DFA8_max = DFA.unpackEncodedStringToUnsignedChars(DFA8_maxS);
+	static final short[] DFA8_accept = DFA.unpackEncodedString(DFA8_acceptS);
+	static final short[] DFA8_special = DFA.unpackEncodedString(DFA8_specialS);
+	static final short[][] DFA8_transition;
+
+	static {
+		int numStates = DFA8_transitionS.length;
+		DFA8_transition = new short[numStates][];
+		for (int i=0; i<numStates; i++) {
+			DFA8_transition[i] = DFA.unpackEncodedString(DFA8_transitionS[i]);
+		}
+	}
+
+	protected class DFA8 extends DFA {
+
+		public DFA8(BaseRecognizer recognizer) {
+			this.recognizer = recognizer;
+			this.decisionNumber = 8;
+			this.eot = DFA8_eot;
+			this.eof = DFA8_eof;
+			this.min = DFA8_min;
+			this.max = DFA8_max;
+			this.accept = DFA8_accept;
+			this.special = DFA8_special;
+			this.transition = DFA8_transition;
+		}
+		@Override
+		public String getDescription() {
+			return "43:10: fragment B_EXPR : ( EXPR ':' ( '-' )? REG_EXPR | ( '-' )? REG_EXPR );";
+		}
+	}
+
+	static final String DFA17_eotS =
+		"\3\uffff\1\13\1\uffff\2\13\2\uffff\2\13\1\uffff\1\21\3\uffff\1\13\1\uffff"+
+		"\2\13\1\uffff";
+	static final String DFA17_eofS =
+		"\25\uffff";
+	static final String DFA17_minS =
+		"\1\11\2\uffff\1\41\1\uffff\2\41\2\uffff\2\41\1\uffff\1\52\3\uffff\1\41"+
+		"\1\uffff\2\41\1\uffff";
+	static final String DFA17_maxS =
+		"\1\172\2\uffff\1\172\1\uffff\1\172\1\75\2\uffff\2\172\1\uffff\1\172\3"+
+		"\uffff\1\172\1\uffff\2\172\1\uffff";
+	static final String DFA17_acceptS =
+		"\1\uffff\1\1\1\2\1\uffff\1\4\2\uffff\1\7\1\13\2\uffff\1\5\1\uffff\1\10"+
+		"\1\11\1\12\1\uffff\1\6\2\uffff\1\3";
+	static final String DFA17_specialS =
+		"\25\uffff}>";
+	static final String[] DFA17_transitionS = {
+			"\2\4\2\uffff\1\4\22\uffff\1\4\1\7\4\uffff\1\1\2\uffff\1\2\3\uffff\1\10"+
+			"\3\uffff\3\6\10\uffff\2\7\3\uffff\14\5\1\3\15\5\6\uffff\32\5",
+			"",
+			"",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\16\12\1\11\13\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\32\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"\1\16\15\uffff\1\15\12\uffff\1\17\1\uffff\2\16",
+			"",
+			"",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\21\12\1\20\10\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\32\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"",
+			"\2\14\2\uffff\1\14\1\uffff\12\14\5\uffff\1\14\1\uffff\35\14\1\uffff"+
+			"\1\14\1\uffff\32\14",
+			"",
+			"",
+			"",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\17\12\1\22\12\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"",
+			"\1\16\10\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff\2\16\1\uffff"+
+			"\1\14\1\uffff\7\12\1\23\22\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			"\1\16\6\uffff\1\24\1\uffff\2\14\2\uffff\1\14\1\15\12\12\1\17\1\uffff"+
+			"\2\16\1\uffff\1\14\1\uffff\32\12\3\14\1\uffff\1\12\1\uffff\32\12",
+			""
+	};
+
+	static final short[] DFA17_eot = DFA.unpackEncodedString(DFA17_eotS);
+	static final short[] DFA17_eof = DFA.unpackEncodedString(DFA17_eofS);
+	static final char[] DFA17_min = DFA.unpackEncodedStringToUnsignedChars(DFA17_minS);
+	static final char[] DFA17_max = DFA.unpackEncodedStringToUnsignedChars(DFA17_maxS);
+	static final short[] DFA17_accept = DFA.unpackEncodedString(DFA17_acceptS);
+	static final short[] DFA17_special = DFA.unpackEncodedString(DFA17_specialS);
+	static final short[][] DFA17_transition;
+
+	static {
+		int numStates = DFA17_transitionS.length;
+		DFA17_transition = new short[numStates][];
+		for (int i=0; i<numStates; i++) {
+			DFA17_transition[i] = DFA.unpackEncodedString(DFA17_transitionS[i]);
+		}
+	}
+
+	protected class DFA17 extends DFA {
+
+		public DFA17(BaseRecognizer recognizer) {
+			this.recognizer = recognizer;
+			this.decisionNumber = 17;
+			this.eot = DFA17_eot;
+			this.eof = DFA17_eof;
+			this.min = DFA17_min;
+			this.max = DFA17_max;
+			this.accept = DFA17_accept;
+			this.special = DFA17_special;
+			this.transition = DFA17_transition;
+		}
+		@Override
+		public String getDescription() {
+			return "1:1: Tokens : ( T__15 | T__16 | T__17 | WS | EXPR | REG_EXPR | OP | AVEXPR1 | AVEXPR3 | AVEXPR5 | AVEXPR6 );";
+		}
+	}
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHParser.java
new file mode 100644
index 0000000..f473d69
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opMORPHParser.java
@@ -0,0 +1,362 @@
+// $ANTLR 3.5.3 c2ps_opMORPH.g 2026-07-28 12:36:06
+package de.ids_mannheim.korap.query.parse.cosmas;
+
+import org.antlr.runtime.*;
+import java.util.Stack;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.antlr.runtime.tree.*;
+
+
+@SuppressWarnings("all")
+public class c2ps_opMORPHParser extends Parser {
+	public static final String[] tokenNames = new String[] {
+		"<invalid>", "<EOR>", "<DOWN>", "<UP>", "AVEXPR1", "AVEXPR3", "AVEXPR5", 
+		"AVEXPR6", "B_EXPR", "EMPTY", "EXPR", "OP", "OPMORPH", "REG_EXPR", "WS", 
+		"'&'", "')'", "'MORPH('"
+	};
+	public static final int EOF=-1;
+	public static final int T__15=15;
+	public static final int T__16=16;
+	public static final int T__17=17;
+	public static final int AVEXPR1=4;
+	public static final int AVEXPR3=5;
+	public static final int AVEXPR5=6;
+	public static final int AVEXPR6=7;
+	public static final int B_EXPR=8;
+	public static final int EMPTY=9;
+	public static final int EXPR=10;
+	public static final int OP=11;
+	public static final int OPMORPH=12;
+	public static final int REG_EXPR=13;
+	public static final int WS=14;
+
+	// delegates
+	public Parser[] getDelegates() {
+		return new Parser[] {};
+	}
+
+	// delegators
+
+
+	public c2ps_opMORPHParser(TokenStream input) {
+		this(input, new RecognizerSharedState());
+	}
+	public c2ps_opMORPHParser(TokenStream input, RecognizerSharedState state) {
+		super(input, state);
+	}
+
+	protected TreeAdaptor adaptor = new CommonTreeAdaptor();
+
+	public void setTreeAdaptor(TreeAdaptor adaptor) {
+		this.adaptor = adaptor;
+	}
+	public TreeAdaptor getTreeAdaptor() {
+		return adaptor;
+	}
+	@Override public String[] getTokenNames() { return c2ps_opMORPHParser.tokenNames; }
+	@Override public String getGrammarFileName() { return "c2ps_opMORPH.g"; }
+
+
+	public static class opMORPH_return extends ParserRuleReturnScope {
+		Object tree;
+		@Override
+		public Object getTree() { return tree; }
+	};
+
+
+	// $ANTLR start "opMORPH"
+	// c2ps_opMORPH.g:58:1: opMORPH : ( 'MORPH(' ')' EOF -> ^( OPMORPH EMPTY ) | 'MORPH(' avExpr ( ( '&' )? avExpr )* ')' EOF -> ^( OPMORPH ( avExpr )+ ) );
+	public final c2ps_opMORPHParser.opMORPH_return opMORPH() throws RecognitionException {
+		c2ps_opMORPHParser.opMORPH_return retval = new c2ps_opMORPHParser.opMORPH_return();
+		retval.start = input.LT(1);
+
+		Object root_0 = null;
+
+		Token string_literal1=null;
+		Token char_literal2=null;
+		Token EOF3=null;
+		Token string_literal4=null;
+		Token char_literal6=null;
+		Token char_literal8=null;
+		Token EOF9=null;
+		ParserRuleReturnScope avExpr5 =null;
+		ParserRuleReturnScope avExpr7 =null;
+
+		Object string_literal1_tree=null;
+		Object char_literal2_tree=null;
+		Object EOF3_tree=null;
+		Object string_literal4_tree=null;
+		Object char_literal6_tree=null;
+		Object char_literal8_tree=null;
+		Object EOF9_tree=null;
+		RewriteRuleTokenStream stream_15=new RewriteRuleTokenStream(adaptor,"token 15");
+		RewriteRuleTokenStream stream_16=new RewriteRuleTokenStream(adaptor,"token 16");
+		RewriteRuleTokenStream stream_17=new RewriteRuleTokenStream(adaptor,"token 17");
+		RewriteRuleTokenStream stream_EOF=new RewriteRuleTokenStream(adaptor,"token EOF");
+		RewriteRuleSubtreeStream stream_avExpr=new RewriteRuleSubtreeStream(adaptor,"rule avExpr");
+
+		try {
+			// c2ps_opMORPH.g:58:9: ( 'MORPH(' ')' EOF -> ^( OPMORPH EMPTY ) | 'MORPH(' avExpr ( ( '&' )? avExpr )* ')' EOF -> ^( OPMORPH ( avExpr )+ ) )
+			int alt3=2;
+			int LA3_0 = input.LA(1);
+			if ( (LA3_0==17) ) {
+				int LA3_1 = input.LA(2);
+				if ( (LA3_1==16) ) {
+					alt3=1;
+				}
+				else if ( ((LA3_1 >= AVEXPR1 && LA3_1 <= AVEXPR6)||LA3_1==EXPR||LA3_1==REG_EXPR) ) {
+					alt3=2;
+				}
+
+				else {
+					int nvaeMark = input.mark();
+					try {
+						input.consume();
+						NoViableAltException nvae =
+							new NoViableAltException("", 3, 1, input);
+						throw nvae;
+					} finally {
+						input.rewind(nvaeMark);
+					}
+				}
+
+			}
+
+			else {
+				NoViableAltException nvae =
+					new NoViableAltException("", 3, 0, input);
+				throw nvae;
+			}
+
+			switch (alt3) {
+				case 1 :
+					// c2ps_opMORPH.g:58:11: 'MORPH(' ')' EOF
+					{
+					string_literal1=(Token)match(input,17,FOLLOW_17_in_opMORPH425);  
+					stream_17.add(string_literal1);
+
+					char_literal2=(Token)match(input,16,FOLLOW_16_in_opMORPH427);  
+					stream_16.add(char_literal2);
+
+					EOF3=(Token)match(input,EOF,FOLLOW_EOF_in_opMORPH435);  
+					stream_EOF.add(EOF3);
+
+
+					// AST REWRITE
+					// elements: 
+					// token labels: 
+					// rule labels: retval
+					// token list labels: 
+					// rule list labels: 
+					// wildcard labels: 
+					retval.tree = root_0;
+					RewriteRuleSubtreeStream stream_retval=new RewriteRuleSubtreeStream(adaptor,"rule retval",retval!=null?retval.getTree():null);
+
+					root_0 = (Object)adaptor.nil();
+					// 58:34: -> ^( OPMORPH EMPTY )
+					{
+						// c2ps_opMORPH.g:58:37: ^( OPMORPH EMPTY )
+						{
+						Object root_1 = (Object)adaptor.nil();
+						root_1 = (Object)adaptor.becomeRoot((Object)adaptor.create(OPMORPH, "OPMORPH"), root_1);
+						adaptor.addChild(root_1, (Object)adaptor.create(EMPTY, "EMPTY"));
+						adaptor.addChild(root_0, root_1);
+						}
+
+					}
+
+
+					retval.tree = root_0;
+
+					}
+					break;
+				case 2 :
+					// c2ps_opMORPH.g:59:5: 'MORPH(' avExpr ( ( '&' )? avExpr )* ')' EOF
+					{
+					string_literal4=(Token)match(input,17,FOLLOW_17_in_opMORPH449);  
+					stream_17.add(string_literal4);
+
+					pushFollow(FOLLOW_avExpr_in_opMORPH451);
+					avExpr5=avExpr();
+					state._fsp--;
+
+					stream_avExpr.add(avExpr5.getTree());
+					// c2ps_opMORPH.g:59:21: ( ( '&' )? avExpr )*
+					loop2:
+					while (true) {
+						int alt2=2;
+						int LA2_0 = input.LA(1);
+						if ( ((LA2_0 >= AVEXPR1 && LA2_0 <= AVEXPR6)||LA2_0==EXPR||LA2_0==REG_EXPR||LA2_0==15) ) {
+							alt2=1;
+						}
+
+						switch (alt2) {
+						case 1 :
+							// c2ps_opMORPH.g:59:22: ( '&' )? avExpr
+							{
+							// c2ps_opMORPH.g:59:22: ( '&' )?
+							int alt1=2;
+							int LA1_0 = input.LA(1);
+							if ( (LA1_0==15) ) {
+								alt1=1;
+							}
+							switch (alt1) {
+								case 1 :
+									// c2ps_opMORPH.g:59:22: '&'
+									{
+									char_literal6=(Token)match(input,15,FOLLOW_15_in_opMORPH454);  
+									stream_15.add(char_literal6);
+
+									}
+									break;
+
+							}
+
+							pushFollow(FOLLOW_avExpr_in_opMORPH457);
+							avExpr7=avExpr();
+							state._fsp--;
+
+							stream_avExpr.add(avExpr7.getTree());
+							}
+							break;
+
+						default :
+							break loop2;
+						}
+					}
+
+					char_literal8=(Token)match(input,16,FOLLOW_16_in_opMORPH461);  
+					stream_16.add(char_literal8);
+
+					EOF9=(Token)match(input,EOF,FOLLOW_EOF_in_opMORPH464);  
+					stream_EOF.add(EOF9);
+
+
+					// AST REWRITE
+					// elements: avExpr
+					// token labels: 
+					// rule labels: retval
+					// token list labels: 
+					// rule list labels: 
+					// wildcard labels: 
+					retval.tree = root_0;
+					RewriteRuleSubtreeStream stream_retval=new RewriteRuleSubtreeStream(adaptor,"rule retval",retval!=null?retval.getTree():null);
+
+					root_0 = (Object)adaptor.nil();
+					// 59:45: -> ^( OPMORPH ( avExpr )+ )
+					{
+						// c2ps_opMORPH.g:59:48: ^( OPMORPH ( avExpr )+ )
+						{
+						Object root_1 = (Object)adaptor.nil();
+						root_1 = (Object)adaptor.becomeRoot((Object)adaptor.create(OPMORPH, "OPMORPH"), root_1);
+						if ( !(stream_avExpr.hasNext()) ) {
+							throw new RewriteEarlyExitException();
+						}
+						while ( stream_avExpr.hasNext() ) {
+							adaptor.addChild(root_1, stream_avExpr.nextTree());
+						}
+						stream_avExpr.reset();
+
+						adaptor.addChild(root_0, root_1);
+						}
+
+					}
+
+
+					retval.tree = root_0;
+
+					}
+					break;
+
+			}
+			retval.stop = input.LT(-1);
+
+			retval.tree = (Object)adaptor.rulePostProcessing(root_0);
+			adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+		}
+		catch (RecognitionException re) {
+			reportError(re);
+			recover(input,re);
+			retval.tree = (Object)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+		}
+		finally {
+			// do for sure before leaving
+		}
+		return retval;
+	}
+	// $ANTLR end "opMORPH"
+
+
+	public static class avExpr_return extends ParserRuleReturnScope {
+		Object tree;
+		@Override
+		public Object getTree() { return tree; }
+	};
+
+
+	// $ANTLR start "avExpr"
+	// c2ps_opMORPH.g:61:1: avExpr : ( AVEXPR1 | AVEXPR3 | AVEXPR5 | AVEXPR6 | EXPR | REG_EXPR );
+	public final c2ps_opMORPHParser.avExpr_return avExpr() throws RecognitionException {
+		c2ps_opMORPHParser.avExpr_return retval = new c2ps_opMORPHParser.avExpr_return();
+		retval.start = input.LT(1);
+
+		Object root_0 = null;
+
+		Token set10=null;
+
+		Object set10_tree=null;
+
+		try {
+			// c2ps_opMORPH.g:61:8: ( AVEXPR1 | AVEXPR3 | AVEXPR5 | AVEXPR6 | EXPR | REG_EXPR )
+			// c2ps_opMORPH.g:
+			{
+			root_0 = (Object)adaptor.nil();
+
+
+			set10=input.LT(1);
+			if ( (input.LA(1) >= AVEXPR1 && input.LA(1) <= AVEXPR6)||input.LA(1)==EXPR||input.LA(1)==REG_EXPR ) {
+				input.consume();
+				adaptor.addChild(root_0, (Object)adaptor.create(set10));
+				state.errorRecovery=false;
+			}
+			else {
+				MismatchedSetException mse = new MismatchedSetException(null,input);
+				throw mse;
+			}
+			}
+
+			retval.stop = input.LT(-1);
+
+			retval.tree = (Object)adaptor.rulePostProcessing(root_0);
+			adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+		}
+		catch (RecognitionException re) {
+			reportError(re);
+			recover(input,re);
+			retval.tree = (Object)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+		}
+		finally {
+			// do for sure before leaving
+		}
+		return retval;
+	}
+	// $ANTLR end "avExpr"
+
+	// Delegated rules
+
+
+
+	public static final BitSet FOLLOW_17_in_opMORPH425 = new BitSet(new long[]{0x0000000000010000L});
+	public static final BitSet FOLLOW_16_in_opMORPH427 = new BitSet(new long[]{0x0000000000000000L});
+	public static final BitSet FOLLOW_EOF_in_opMORPH435 = new BitSet(new long[]{0x0000000000000002L});
+	public static final BitSet FOLLOW_17_in_opMORPH449 = new BitSet(new long[]{0x00000000000024F0L});
+	public static final BitSet FOLLOW_avExpr_in_opMORPH451 = new BitSet(new long[]{0x000000000001A4F0L});
+	public static final BitSet FOLLOW_15_in_opMORPH454 = new BitSet(new long[]{0x00000000000024F0L});
+	public static final BitSet FOLLOW_avExpr_in_opMORPH457 = new BitSet(new long[]{0x000000000001A4F0L});
+	public static final BitSet FOLLOW_16_in_opMORPH461 = new BitSet(new long[]{0x0000000000000000L});
+	public static final BitSet FOLLOW_EOF_in_opMORPH464 = new BitSet(new long[]{0x0000000000000002L});
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opWF.java b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opWF.java
index 2bfb513..cc51841 100644
--- a/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opWF.java
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/cosmas/c2ps_opWF.java
@@ -5,6 +5,7 @@
 import de.ids_mannheim.korap.query.parse.cosmas.c2ps_opPROXLexer;
 import de.ids_mannheim.korap.query.parse.cosmas.c2ps_opPROX;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import de.ids_mannheim.korap.util.TokenUtils;
 
 /*
  * parses prefixed and suffixed options of a search wordform.
@@ -14,8 +15,9 @@
 public class c2ps_opWF
 
 {
-	static final boolean bDebug = false; 
-
+	static final boolean bDebug 	= false; 
+	static final boolean showTokens	= false;
+	
     /* check:
      * Arguments:
      * bStrip: true: 'input' contains "wort" -> strip " away -> wort.
@@ -27,7 +29,10 @@
 
     public static Tree check (String input, boolean bStrip, boolean bLem, int pos) 
     {
-    	if (bStrip)
+        if( bDebug )
+        	System.out.printf("Debug: opWF.check: input='%s' pos=%d.\n", input, pos);
+                
+        if (bStrip)
             input = input.substring(1, input.length() - 1);
 
         if (bLem && input.charAt(0) == '&') {
@@ -38,15 +43,14 @@
         ANTLRStringStream ss = new ANTLRStringStream(input);
         c2ps_opWFLexer lex = new c2ps_opWFLexer(ss);
         CommonTokenStream tokens = new CommonTokenStream(lex);
+        
+        if( bDebug && showTokens )
+        	TokenUtils.printLexerTokens(tokens, "opWF");
+        
         c2ps_opWFParser g = new c2ps_opWFParser(tokens);
         c2ps_opWFParser.searchWFs_return c2PQWFReturn = null;
         c2ps_opWFParser.searchLEM_return c2PQLEMReturn = null;
 
-        /*
-        System.out.println("check opWF:" + index + ": " + input);
-        System.out.flush();
-        */
-
         try {
             if (bLem)
                 c2PQLEMReturn = g.searchLEM(pos);
@@ -57,6 +61,11 @@
             e.printStackTrace();
         }
 
+        // convert OPWF(Haus (TPOS sa)) -> OPBED( (OPWF(Haus )(TPOS ..sa)).
+        // reason: the serializer can handle (TPOS...) inside OPBEG, but not inside OPWF(...).
+        // to avoid double implementation of TPOS serialization, insert (OPWF TPOS) into (OPBED...).
+        // 11.06.26/FB
+        
         // AST Tree anzeigen:
         Tree tree = bLem ? (Tree)c2PQLEMReturn.getTree() : (Tree)c2PQWFReturn.getTree();
         
@@ -67,6 +76,9 @@
         	 System.out.flush();
         	 }
 
+       if( bDebug )
+        	System.out.printf("Debug: opWF.check: returning tree='%s'.\n", tree.toStringTree());
+
         return tree;
     }
 
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 11bc23b..5d2c744 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
@@ -15,14 +15,17 @@
 import de.ids_mannheim.korap.query.serialize.util.Antlr3DescriptiveErrorListener;
 import de.ids_mannheim.korap.query.serialize.util.Converter;
 import de.ids_mannheim.korap.query.serialize.util.KoralObjectGenerator;
+import de.ids_mannheim.korap.query.serialize.util.MORPH_Mapper;
 import de.ids_mannheim.korap.query.serialize.util.ResourceMapper;
 import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
-import de.ids_mannheim.korap.util.StringUtils;
+import de.ids_mannheim.korap.util.*;
 
 import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.CommonTokenStream;
 import org.antlr.runtime.FailedPredicateException;
 import org.antlr.runtime.RecognitionException;
 import org.antlr.runtime.Token;
+import org.antlr.runtime.tree.CommonTree;
 import org.antlr.runtime.tree.Tree;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.slf4j.Logger;
@@ -43,12 +46,15 @@
  * @author Joachim Bingel (bingel@ids-mannheim.de)
  * @author Nils Diewald (diewald@ids-mannheim.de)
  * @author Eliza Margaretha (margaretha@ids-mannheim.de)
- * @version 0.3
+ * @author Franck Bodmer (bodmer@ids-mannheim.de)
+ * @version 0.4 -- 09.06.26/FB
  */
 public class Cosmas2QueryProcessor extends Antlr3AbstractQueryProcessor {
 
-    private static final boolean DEBUG = false;
-
+	private static final boolean DEBUG 		 	= false;
+	private static final boolean bShowTokens 	= false;
+	private static final boolean showRegGroups	= false;
+	
     private static Logger log =
             LoggerFactory.getLogger(Cosmas2QueryProcessor.class);
 
@@ -56,6 +62,15 @@
     
     private LinkedList<Map<String, Object>[]> toWrapStack =
             new LinkedList<Map<String, Object>[]>();
+    
+    // default layer for part of speech: 'p'; 'pos' is for Poliqarp.
+    private static final String LAYER_POS   = "p"; 
+    private static final String LAYER_S	 	= "s"; // structure layer for span: e.g. <s>, <p>, <head>, etc. 
+
+    // types for handling of #ELEM() and MORPH()
+    private static final int opELEM			= 1;
+    private static final int opMORPH		= 2;
+    
     /**
      * Field for repetition query (Kleene + or * operations, or
      * min/max queries: {2,4}
@@ -85,8 +100,8 @@
     private boolean inOPALL = false;
     private boolean inOPNHIT = false;
     /**
-             *
-             */
+     *
+     */
     private int classCounter = 1;
     private boolean negate = false;
 
@@ -136,6 +151,30 @@
     public static Pattern wildcardPlusPattern = Pattern.compile("([+])");
     public static Pattern wildcardQuestionPattern = Pattern.compile("([?])");
 
+	static void printLexerTokens(CommonTokenStream tokens )
+		
+	{
+		if( tokens == null )
+			{
+			System.out.printf("Debug: Tokens: empty!\n");
+			return;
+			}
+		
+		tokens.fill();
+		
+		if( tokens.size() == 0 )
+			{
+			System.out.printf("Debug: Tokens: empty!\n");
+			return;
+			}
+		
+		for( Token token : tokens.getTokens())
+			{
+			//System.out.printf("Debug: Token: '%s'.\n",  token.toString());
+			System.out.printf("Debug: Token: '%s'.\n",  token.toString());
+			}
+	}
+
 	/**
 	 * reportErrorsinTree:
 	 * - traverse the AST tree and search for nodes of type ERROR, they contain
@@ -483,7 +522,10 @@
         // See C-II QL documentation for more detail:
         // http://www.ids-mannheim.de/cosmas2/win-app/hilfe/suchanfrage/eingabe-grafisch/syntax/textpositionen.html
 
-        // Step I: create group
+    	if( DEBUG )
+    		System.out.printf("Debug: processOPBED: '%s'.\n",  node.toStringTree());
+    	
+    	// Step I: create group
         int optsChild = node.getChildCount() - 1;
         Tree begConditions =
                 getFirstChildWithCat(node.getChild(optsChild), "TPBEG");
@@ -503,13 +545,13 @@
                 new ArrayList<Map<String, Object>>();
         if (begConditions != null) {
             for (Tree condition : getChildren(begConditions)) {
-                conditionGroups.add(processPositionCondition(condition,
+            	conditionGroups.add(processPositionCondition(condition,
                         distributedOperands, "beg"));
             }
         }
         if (endConditions != null) {
             for (Tree condition : getChildren(endConditions)) {
-                conditionGroups.add(processPositionCondition(condition,
+            	conditionGroups.add(processPositionCondition(condition,
                         distributedOperands, "end"));
             }
         }
@@ -1003,167 +1045,794 @@
     }
 
 
-    // TODO: The handling of attributes vs. element names is somehow disputable ...
+    private void printOPELEM(Tree node)
+    {
+    	int nChilds = node.getChildCount();
+    	
+		System.out.printf("Debug: processOPELEM: Tree node'%s'.\n", node.toStringTree());
+		System.out.printf("Debug: processOPELEM: #childs: %d.\n", nChilds);
+		
+		for(int i=0; i<nChilds; i++)
+		{
+			Tree child = node.getChild(i);
+			System.out.printf("Debug: processOPELEM: child[%d]='%s' #=%d.\n", i, child.toStringTree(),
+					child.getChildCount());
+		}
+    }
+   
+    /*
+     * isLayerForPOS()
+     * 
+     * returns true if layer applies to a POS query, i.e. a token Query.
+     *         false in all other cases, i.e. they apply to a span query.
+     * - foundries are not handled in Koral, so they cannot be used to determine
+     *   if a query relates to token or span.
+     * - "ne" = named entities: span or token?
+     * 13.05.26/FB
+     */
+    
+    private boolean isLayerForPOS(String layer)
+    
+    {
+    	if( layer.equals("p") || layer.equals("l") || layer.equals("m") || layer.equals("ne") )
+    		return true;
+    	
+    	return false; // not for POS
+    }
+    	
+    /* addSubVal()
+     * 
+     * sets fields "key" and "value" from attVal.
+     * if attVal == "v1:v2" -> key : "v1", value : "v2"
+     * else -> value : "attVal".
+     * return: 
+     * 		true if a negative value is stored in field 'value'. E.g. Attval = "number:-PL",
+     * 		false if not. 
+     * 05.05.26/FB
+     */
+    private boolean addSubVal(Map<String, Object> term, String attVal)
+    
+    {
+    	boolean
+    		isNeg;
+    	String[]
+    		splitted = attVal.split(":");
+    	/*
+    	System.out.printf("Debug: addSubVal: attVal='%s' splitted.size=%d.\n", attVal, splitted.length);
+    	if( splitted.length > 1)
+    		System.out.printf("Debug: addSubVal: splitted[0]='%s' %d, splitted[1]='%s' %d.\n", splitted[0], splitted[0].length(), splitted[1], splitted[1].length());
+		*/
+    	
+    	// avoid special case: attVal=":abc" -> splitted.length = 2 & splitted[0] = '', or splitted.length > 2, e.g. attVal="abc:def:ghi" with multiple ':'.
+    	if( splitted.length == 2 && splitted[0].length() > 0 && splitted[1].length() > 0 )
+    		{ // attVal = "key:value", e.g. "gender:fem".
+    		term.put("key", splitted[0]);
+    		isNeg = addNegVal(term, "value", splitted[1]);
+    		}
+    	else
+    		{ // not splitted: store key : attVal.
+    		isNeg = addNegVal(term, "key", attVal);
+    		}
+    	
+    	return isNeg;
+    }
+    
+    /* addNegVal:
+     * - add AttVal to field 'field' of 'term'.
+     * - if attVal ='-SG' : add 'SG' to the field and return isNeg=true.
+     *      else: add AttVal as is and return isNeg=false.
+     * 20.04.26/FB
+     */
+    
+    private boolean addNegVal(Map<String, Object> term, String field, String attVal)
+    
+    {
+    if( DEBUG )
+    	System.out.printf("Debug: addNegVal: attVal = '%s'.\n", attVal);
+   
+   	if( attVal.charAt(0) == '-' )
+		{
+		term.put(field, attVal.substring(1)); // skip '-'.
+		return true;
+		}
+	else
+		{
+		term.put(field, attVal);
+		return false;
+		}
+	} // addNegValue
+
+    /* addAttVal:
+     * 
+     * - normal case: adds key=attName and value=AttVal to 'term'.
+     * - POS (part of speech) case: iPOS=true or attName='ana':
+     *   adds key=attVal.
+     *   attName is not added, but it switches to layer=LAYER_POS, so 'value' is unused.
+     *   attVal: '-Val' is converted to key=Val (without '-') and match=NOT_EQUALS.
+     * - returns true if layer is specified and it applies to a part of speech query (isLayerPOS = true). 
+     *           e.g. layer=p
+     * 17.04.26/FB
+     * 11.05.26/FB all layers added which apply to a POS query -> this is dereko specific!
+     */
+    
+    private boolean addAttVal(Map<String, Object> term, boolean isPOS, String nodeCat, String attName, String attVal)
+    
+    {
+    boolean 
+    	isNeg 		= false,
+    	isLayerPOS 	= false;
+    String[] 
+        	splitted = attName.split("/");
+        
+    if( DEBUG )
+    	System.out.printf("Debug: addAttVal: isPOS=%b attName='%s' attVal='%s'.\n",
+    			isPOS, attName, attVal);
+    
+    if (splitted.length > 1) 
+    	{ // foundry+layer specified: #ELEM(dereko/s=val).
+    	if(DEBUG)
+    		System.out.printf("Debug: processOPELEM: splitted[0]='%s' splitted[1]='%s'.\n",
+    				splitted[0], splitted[1]);
+    	
+        term.put("foundry", splitted[0]);
+        term.put("layer",   splitted[1]);
+        isNeg = addSubVal(term, attVal);
+       
+        if( isLayerForPOS(splitted[1]) )
+        	isLayerPOS = true;
+        }
+    else if( isPOS || attName.equals("ana") )
+    	{ // case for part of speech POS: isPOS: #ELEM(W ana='N'), or att='ana': #ELEM(ana='N').
+    	  // if attVal == -value : set NOT_EQUAL and remove negation mark.
+    	if( attName.equals("ana") == true )
+    		{ // 'ana' is not stored. a. attVal="NOU" -> store "key" = attVal (no "value");
+    		  // b. attVal="gender:fem" -> store "key" = "gender" and "value" = "fem":
+    		isNeg = addSubVal(term, attVal);
+            }
+    	else
+    		{
+    		// still layer=p (isPOS==true), but key == value should be searched as this is not a POS att-value-pair.
+    		// no attVal = "x:y" expected.
+    		term.put("key", attName);
+            isNeg = addNegVal(term, "value", attVal);
+    		}
+    		    		
+    	term.put("layer", LAYER_POS);
+    	}
+    else
+    	{ // normal case: layer = s, not a POS:
+    	  // no attVal = "x:y" expected.
+		term.put("key", attName);
+        term.put("layer", "s"); // "s" is default for #ELEM().
+        isNeg = addNegVal(term, "value", attVal);
+    	}
+    
+    // consider the negation mark in front of the key, too:
+    KoralMatchOperator match =
+            isNeg == true || nodeCat.equals("NOTEQ")
+                    ? KoralMatchOperator.NOT_EQUALS
+                    : KoralMatchOperator.EQUALS;
+    
+    term.put("match", match.toString());
+    return isLayerPOS;
+	} // addAttVal
+
+    /* hasPOS()
+     * - returns true if tree 'node' has a 'W' element name or, if there is not element name specified,
+     *   any of its attribute is 'ana'.
+     * - returns false else.
+     * - E.g. #ELEM(W ana=NOU), #ELEM(ana=NOU), #ELEM(W): return true.
+     *        #ELEM(Head), #ELEM(head ana=top): return false.
+     * 24.04.26/FB
+     * 
+     */
+    private boolean hasPOS(Tree node)
+    
+    {
+    	Tree
+    		elNode = getFirstChildWithCat(node, "ELNAME");
+    	
+        if( elNode != null )
+        	{ // if element has a name, it must be "W".
+            if( elNode.getChild(0).toString().toLowerCase().equals("w") )
+	        	return true;
+	        else
+            	return false;
+	    	}
+        	
+        // if element has no name, see if it has an attribute name = "ana":
+    	for (int i = 0; i < node.getChildCount(); i++) 
+        	{
+    		Tree 
+    			child = node.getChild(i);
+    		if( child.getChildCount() > 0)
+    			{
+	    		String
+	    			attName = child.getChild(0).toStringTree().toLowerCase(); // attr. name lowered.
+	    		if( attName.equals("ana") )
+	            	return true;
+	        	}
+        	}
+        return false;
+    }
+    
+    /* TODO: The handling of attributes vs. element names is somehow disputable ...
+     * E.g. Tree node'(OPELEM (ELNAME BoBo) (EQ type TOP) (EQ ana N SG))'
+     * -> 3 children: (ELNAME Bobo), (EQ type TOP), (EQ ana N SG).
+     *    child[0]='(ELNAME BoBo)' #subChildren=1.
+		  child[1]='(EQ type TOP)' #subChildren=2.
+	 	  child[2]='(EQ ana N SG)' #subChildren=3.
+	 *
+     * - Element name expected by the grammar should be a single name without foundry/layer:
+     *   e.g. "HEAD"          : (ELNAME head) : OK; 
+     *        "dereko/s=HEAD" : (EQ dereko/s head) : node ELNAME is lost!
+     * Corrections: 
+     * 24.03.26/FB missing default layer 's' added.
+     * 26.03.26/FB correct field names to attributes.
+     * 27.03.26/FB attribute name to lowercase, attribute value not: corrected.
+     *             element with several attributes and attributes with several values: corrected.
+     * 23.04.26/FB spec. case: #ELEM(w ana=..) is a Koral:term, not a Koral:span.
+     */
+    
     @SuppressWarnings("unchecked")
-    private void processOPELEM (Tree node) {
-        // Step I: create element
-        Map<String, Object> span = KoralObjectGenerator.makeSpan();
+    private void processOPELEM (Tree node) 
+    {
+    	if( DEBUG )
+    		printOPELEM(node);
+    
+        // Step I: create element: 'span' = a span or a term (spec. case: #ELEM(w).
+        Map<String, Object> 
+        	span; // = KoralObjectGenerator.makeSpan();
+        boolean
+        	isLayerPOS = false;
+        
+        if( hasPOS(node))
+        	span = KoralObjectGenerator.makeToken(); // not makeTerm().
+        else
+        	span = KoralObjectGenerator.makeSpan();
+        
         if (node.getChild(0).toStringTree().equals("EMPTY")) {
             addError(StatusCodes.MALFORMED_QUERY, "Empty #ELEM() operator."
-                    + " Please specify a valid element key (like 's' for sentence).");
+                    + " Please specify a valid element name (like 's' for sentence).");
             return;
         }
-        else {
-            int elname = 0;
-            Tree elnameNode = getFirstChildWithCat(node, "ELNAME");
+       
+        int elname = 0;
+        boolean 
+        	isPOS = false,	// wordpos = part of speech, if #ELEM(w ana=...).
+        	isW   = false;	// #ELEM(W)
+        
+        Tree elnameNode = getFirstChildWithCat(node, "ELNAME");
+        /*
+        // TODO: This is identical to processOPMORPH
+        String wordOrRegex = "\\w+|\".+?\"";
+        Pattern p = Pattern.compile("((\\w+)/)?((\\w*)(!?=))?(" + wordOrRegex
+        							+ ")(:(" + wordOrRegex + "))?");
+        */
+
+        if (elnameNode != null) 
+        {
+        	if( DEBUG )
+        		System.out.printf("Debug: processOPELEM: elnameNode='%s'.\n", elnameNode.toStringTree());
+
+        	String
+        		elName = elnameNode.getChild(0).toString().toLowerCase();
+
+        	Map<String, Object> 
+            	fm = termToFieldMap(elName); // key=elName.
+
+            if (fm == null) 
+            	return;
+
+            // special keys -> special layers:
+            if( elName.equals("w"))
+            	{
+            	// default layer for word pos. = LAYER_POS.
+            	fm.put("layer", LAYER_POS); 
+            	fm.remove("key"); // remove 'w' as a key as it is not stored in KorAP.
+            	isPOS = true;
+            	isW   = true;
+            	}
+            else
+            	fm.put("layer", LAYER_S); // default layer for span.
+
+        	// add 'wrap' only if not #ELEM(W), i.e. element 'W' withou children:
+            if( !isW || node.getChildCount() > 1 ) 
+            	span.put("wrap", fm);
+	            
+            elname = 1;                
+        }
+        else
+        	{
+        	// for #ELEM( att=val ) without an element name, also add a "wrap" with an empty key;
+        	// otherwise the att/val pair(s) will be inserted outside a "wrap".
+        	Map<String, Object> 
+        		wrap = KoralObjectGenerator.makeTerm();
+            
+            span.put("wrap", wrap); // adds an empty "wrap".
+        	}
+        
+        if (node.getChildCount() > elname) {
             /*
-            // TODO: This is identical to processOPMORPH
-            String wordOrRegex = "\\w+|\".+?\"";
-            Pattern p = Pattern.compile("((\\w+)/)?((\\w*)(!?=))?(" + wordOrRegex
-            							+ ")(:(" + wordOrRegex + "))?");
-            */
-
-            if (elnameNode != null) {
-                /*
-                span.put("key", elnameNode.getChild(0).toStringTree()
-                        .toLowerCase());
-                */
-                Map<String, Object> fm =
-                        termToFieldMap(elnameNode.getChild(0).toStringTree());
-
-                if (fm == null) return;
-
-                // Workaround for things like #ELEM(S) to become #ELEM(s)
-                if (fm.get("foundry") == null && fm.get("layer") == null
-                        && fm.get("key") != null) {
-                    fm.put("key", fm.get("key").toString().toLowerCase());
-                };
-                span.put("wrap", fm);
-                elname = 1;
-
-            }
-
-            if (node.getChildCount() > elname) {
-                /*
-                 * Attributes can carry several values, like #ELEM(W
-                 * ANA != 'N V'), denoting a word whose POS is neither
-                 * N nor V. When seeing this, create a sub-termGroup
-                 * and put it into the top-level term group, but only
-                 * if there are other attributes in that group. If
-                 * not, put the several values as distinct
-                 * attr-val-pairs into the top-level group (in order
-                 * to avoid a top-level group that only contains a
-                 * sub-group).
-                 */
-                Map<String, Object> termGroup = KoralObjectGenerator
-                        .makeTermGroup(KoralTermGroupRelation.AND);
-                ArrayList<Object> termGroupOperands =
-                        (ArrayList<Object>) termGroup.get("operands");
-                for (int i = elname; i < node.getChildCount(); i++) {
-                    Tree attrNode = node.getChild(i);
-                    if (attrNode.getChildCount() == 2) {
+             * Attributes can carry several values, like #ELEM(W
+             * ANA != 'N V'), denoting a word whose POS is neither
+             * N nor V. When seeing this, create a sub-termGroup
+             * and put it into the top-level term group, but only
+             * if there are other attributes in that group. If
+             * not, put the several values as distinct
+             * attr-val-pairs into the top-level group (in order
+             * to avoid a top-level group that only contains a
+             * sub-group).
+             */
+            Map<String, Object> termGroup = KoralObjectGenerator
+                    .makeTermGroup(KoralTermGroupRelation.AND);
+            ArrayList<Object> termGroupOperands =
+                    (ArrayList<Object>) termGroup.get("operands");
+            
+            // 1 child = attribute; foundry/layer not expected for attributes,
+            // they are the same for all attributes:
+            for (int i = elname; i < node.getChildCount(); i++) 
+            {
+                Tree attrNode = node.getChild(i);
+                if( DEBUG ) 
+                	System.out.printf("Debug: processOPELEM: attrNode='%s #=%d'.\n", attrNode.toStringTree(), attrNode.getChildCount());
+                if (attrNode.getChildCount() == 2) 
+                	{
+                    Map<String, Object> term =
+                            KoralObjectGenerator.makeTerm();
+                    termGroupOperands.add(term);
+                    
+                    // String layer = attrNode.getChild(0).toStringTree();
+                    if( DEBUG ) System.out.printf("Debug: processOPELEM: child[0]='%s' child[1]='%s'.\n", 
+                    		attrNode.getChild(0).toStringTree(),
+                    		attrNode.getChild(1).toStringTree());
+                    
+                    String 
+                    	nodeCat = getNodeCat(attrNode),
+                    	attName = attrNode.getChild(0).toStringTree().toLowerCase(), // attr. name lowered.
+                    	attVal  = attrNode.getChild(1).toStringTree();
+                    
+                    if( addAttVal(term, isPOS, nodeCat, attName, attVal) )
+                        isLayerPOS = true;
+                	}
+                else 
+                	{ // childCount != 2:
+                    Map<String, Object> subTermGroup = KoralObjectGenerator
+                            .makeTermGroup(KoralTermGroupRelation.AND);
+                    ArrayList<Object> subTermGroupOperands =
+                            (ArrayList<Object>) subTermGroup.get("operands");
+                    if( DEBUG )
+                    	System.out.printf("Debug: att='%s', #values = %d.\n", 
+                    			attrNode.getChild(0).toStringTree(),
+                    			attrNode.getChildCount());
+                    
+                    if( attrNode.getChild(0).toStringTree().toLowerCase().equals("ana") )
+                    	isPOS = true; // for the case 'W' is not specified.
+                    
+                    int j;
+                    for (j = 1; j < attrNode.getChildCount(); j++) {
                         Map<String, Object> term =
                                 KoralObjectGenerator.makeTerm();
-                        termGroupOperands.add(term);
-                        String layer = attrNode.getChild(0).toStringTree();
-                        String[] splitted = layer.split("/");
-                        if (splitted.length > 1) {
-                            term.put("foundry", splitted[0]);
-                            layer = splitted[1];
+                        if( DEBUG )
+                        	System.out.printf("Debug: att='%s' val[%d]='%s' isPOS=%b.\n", 
+                        			attrNode.getChild(0).toStringTree(), j,
+                        			attrNode.getChild(j).toStringTree(), isPOS);
+                        String 
+                        	nodeCat = getNodeCat(attrNode),
+                        	attName = attrNode.getChild(0).toStringTree().toLowerCase(), // attr. name lowered.
+                        	attVal  = attrNode.getChild(j).toStringTree();
+                        
+                        if( addAttVal(term, isPOS, nodeCat, attName, attVal) )
+                            isLayerPOS = true;
+                        
+                    	if (node.getChildCount() == elname + 1) {
+                            termGroupOperands.add(term);
                         }
-                        term.put("layer", translateMorph(layer));
-                        term.put("key", attrNode.getChild(1).toStringTree());
-                        KoralMatchOperator match =
-                                getNodeCat(attrNode).equals("EQ")
-                                        ? KoralMatchOperator.EQUALS
-                                        : KoralMatchOperator.NOT_EQUALS;
-                        term.put("match", match.toString());
-                    }
-                    else {
-                        Map<String, Object> subTermGroup = KoralObjectGenerator
-                                .makeTermGroup(KoralTermGroupRelation.AND);
-                        ArrayList<Object> subTermGroupOperands =
-                                (ArrayList<Object>) subTermGroup
-                                        .get("operands");
-                        int j;
-                        for (j = 1; j < attrNode.getChildCount(); j++) {
-                            Map<String, Object> term =
-                                    KoralObjectGenerator.makeTerm();
-                            String layer = attrNode.getChild(0).toStringTree();
-                            String[] splitted = layer.split("/");
-                            if (splitted.length > 1) {
-                                term.put("foundry", splitted[0]);
-                                layer = splitted[1];
-                            }
-                            term.put("layer", translateMorph(layer));
-                            term.put("key",
-                                    attrNode.getChild(j).toStringTree());
-                            KoralMatchOperator match =
-                                    getNodeCat(attrNode).equals("EQ")
-                                            ? KoralMatchOperator.EQUALS
-                                            : KoralMatchOperator.NOT_EQUALS;
-                            term.put("match", match.toString());
-                            if (node.getChildCount() == elname + 1) {
-                                termGroupOperands.add(term);
-                            }
-                            else {
-                                subTermGroupOperands.add(term);
-                            }
-                        }
-                        if (node.getChildCount() > elname + 1) {
-                            termGroupOperands.add(subTermGroup);
+                        else {
+                            subTermGroupOperands.add(term);
                         }
                     }
-                    if (getNodeCat(attrNode).equals("NOTEQ")) negate = true;
+                    if (node.getChildCount() > elname + 1) {
+                        termGroupOperands.add(subTermGroup);
+                    }
                 }
-                // possibly only one term was present throughout all
-                // nodes: extract it from the group
-                if (termGroupOperands.size() == 1) {
-                    termGroup = (Map<String, Object>) termGroupOperands.get(0);
-                }
-
-                // TODO: This should be improved ...
-                if (elname == 0) {
-                    span.put("wrap", termGroup);
-                }
-                else {
-                    span.put("attr", termGroup);
-                }
+                if (getNodeCat(attrNode).equals("NOTEQ")) negate = true;
             }
+            // possibly only one term was present throughout all
+            // nodes: extract it from the group
+            if (termGroupOperands.size() == 1) 
+                termGroup = (Map<String, Object>) termGroupOperands.get(0);
+
+            // if 'wrap' exists, add termGroup as 'attr' to 'wrap',
+            // else add termGroup as 'attr'.
+        	Object
+        		wrap = span.get("wrap");
+            
+            if ( wrap != null ) 
+            	{
+            	Map<String, Object> 
+            		fmWrap = (Map <String, Object>)wrap;
+            	fmWrap.put("attr", termGroup);
+            	if( DEBUG )
+            		System.out.printf("Debug: processELEM: adding termGroup to 'attr' to 'wrap'.\n");
+            	}
+            else 
+            	{
+            	if( DEBUG )
+            		System.out.printf("Debug: processElem: no 'wrap': adding termGroup into 'attr'.\n");
+                span.put("attr", termGroup);
+            	}
         }
+        
+        // replace 'span' by 'token' if layer has specified a part of speech:
+        if( isLayerPOS )
+        	span.put("@type",  "koral:token");
+        
         // Step II: decide where to put
         putIntoSuperObject(span);
     }
 
+    /* combineAnnot
+     * - return the first 2 children of node as 1 combination,
+     *   if at least 2 children exist.
+     * 20.06.26/DB
+     */
+    
+    private String combineAnnot(Tree node, int n)
+    
+    {
+    if( node.getChildCount() < n )
+    	return null; // no combination possible.
+    
+    String
+    	combi = "";
 
-    private void processOPMORPH (Tree node) {
+    for(int i=0; i<n; i++)
+	    {
+    	if( i == 0 )
+    		combi = combi.concat(node.getChild(i).toString().toUpperCase());
+    	else
+    		combi = combi.concat(" " + node.getChild(i).toString().toLowerCase());
+	    }
+    
+    if( DEBUG )
+    	System.out.printf("Debug: combineAnnot: combi='%s'.\n", combi);
+    
+    return combi;
+    }
+    
+    /* combineAnnots:
+     * tries combinations of the first 3 or 2 values and looks up for a match in MORPH_Mapper for CONNEXOR tags.
+     * 19.06.26/FB
+     */
+    
+    private int combineAnnots(Tree node, List<String>newAnnots)
+    
+    {
+    final String 
+    	func = "Debug: combineAnnots";
+    String
+    	annotCombi = null,
+    	newAnnot;
+    
+    for(int n=3; n>1; n--)
+    	{
+	    if( node.getChildCount() >= n && (annotCombi = combineAnnot(node, n)) != null )
+		    {
+	    	newAnnot = MORPH_Mapper.translate_CONNEXOR(annotCombi); 
+	        if( newAnnot != null )
+		    	{
+		    	if( DEBUG )
+		    		System.out.printf("%s: combi='%s' -> '%s'-> returns %d.\n",  func, annotCombi, newAnnot, n);
+		    	newAnnots.add(newAnnot);
+		    	return n; // the first n values have been consumed.
+		    	}
+	        else
+		        {
+		        if( DEBUG ) System.out.printf("%s: combi='%s' -> null.\n", func, annotCombi);
+			    }
+		    }
+    	}
+
+    if( DEBUG ) 
+    	System.out.printf("%s: returns 0.\n", func);
+    
+    return 0; // no combination matches, so no. values comsumed.
+    }
+
+    /* rearrangeChildren:
+     * - rearrange order of children of node: POS children first, morph children last,
+     * - keep sort order inside POS and morph values.
+     * - E.g. MORPH(V imp -inf past -pcp) -> MORPH(V -inf -pcp imp past).
+     * - Reason: "V -inf -pcp" must be kept together, because they translate together to a STTS POS;
+     *         "imp", "past" are single morph annotations that translate one by one.
+     * 22.06.26/FB 
+     */
+    
+    private void rearrangeChildren(Tree node, int nChildren)
+    
+    {	final String 
+    		func = "Debug: rearrangeChildren";
+    	ArrayList<String>
+    		children,				// POS and morph children.
+    		morphChildren = null;	// morph children only.
+    	
+    	if( nChildren == 0 )
+    		return; 
+    	
+    	if( DEBUG )
+    		System.out.printf("%s: input: '%s'.\n", func, node.toStringTree());
+    	
+    	children 	  = new ArrayList<String>();
+    	morphChildren = new ArrayList<String>();
+    	
+    	for(int i=0; i<nChildren; i++)
+    		{
+    		String child = node.getChild(i).toString();
+    		
+    		if( MORPH_Mapper.isMorphAnnot(child) == false )
+    			children.add(child);
+    		else
+    			morphChildren.add(child);
+    		}
+    	
+    	// add morph children after POS children:
+    	for(int i=0; i<morphChildren.size(); i++)
+    		children.add( morphChildren.get(i) );
+    	
+    	// overwrite children values in Tree node using order in children list.
+    	for(int i=0; i<nChildren; i++)
+    		{
+    		Tree child 				= node.getChild(i);
+    		CommonTree commonChild  = (CommonTree) child;
+    		commonChild.getToken().setText(children.get(i));
+    		}
+ 
+       	if( DEBUG )
+    		System.out.printf("%s: return node as '%s'.\n", func, node.toStringTree());
+    }
+    
+    /* translate_MORPH_STTS:
+     * 
+     * - translates a MORPH/STTS expression to a STTS annotation,
+     *   i.e. translates from C2-style to STTS-style.
+     * - e.g. "VRB fin a"  -> VAFIN.
+     * - e.g. "VRB fin -a" -> V.FIN -VAFIN.
+     * - e.g. "VRB -fin a" -> V.* - F.FIN.
+     * - e.g. "-VRB fin a" -> -V.*.
+     * - 1st annot expected to be in upper case, the following annots to be in lower case.
+     * Returns: STTS annotation if found, null else.
+     * 16.05.26/FB
+     */
+    
+    private List<String> translate_MORPH_STTS(Tree node)
+    
+    {
+    final String 
+    	func = "Debug: translate_MORPH_STTS";
+    int
+    	nChildren = node.getChildCount();
+    List<String>
+    	annots = new ArrayList<String>();
+    List<String>
+    	annots_STTS = null;
+    int 
+    	iFirst = -1;
+   	String
+		annotSTTS = null; 
+    
+   	for(int i=0; i<nChildren; i++)
+   		{
+   		String child = node.getChild(i).toString();
+   		if( child.startsWith("-") )
+   			{
+   			child = child.substring(1);
+   			if( iFirst == -1 )
+   				iFirst = i;
+   			}
+
+   		if( i==0 )
+   			annots.add(child.toUpperCase());
+   		else
+   			annots.add(child.toLowerCase());
+   		}
+   	
+   	if( iFirst == -1 )
+   		{ // simple translation when no annot. is negative:
+   		annotSTTS = MORPH_Mapper.translate_STTS(annots);
+   		if( annotSTTS != null )
+   			{
+   			annots_STTS = new ArrayList<String>(1);
+   			annots_STTS.add(annotSTTS);
+   	   	   	//System.out.printf("%s: %s translates to: '%s'.\n", func, annots.toString(), annotSTTS == null ? "null" : annotSTTS);
+   	 		}
+   		}
+   	else if( iFirst == 0 )
+	   	{ // "-VRB..." -> "-V.*" 
+   		annotSTTS = MORPH_Mapper.translate_STTS(annots.subList(0, 1));
+   		if( annotSTTS != null )
+   			{
+   			annots_STTS = new ArrayList<String>(1);
+   			annots_STTS.add("-" + annotSTTS);
+   	   	   	//System.out.printf("%s: %s translates to: '%s'.\n", func, annots.toString(), annotSTTS == null ? "null" : annotSTTS);
+   	 		}	
+	   	}
+   	else
+   		{ // this returns a list of 2 STTS annotations.
+   		annots_STTS = MORPH_Mapper.translate_STTS_withNeg(annots, iFirst);
+   		// System.out.printf("%s: %s translates to: '%s'.\n", func, annots.toString(), annots_STTS == null ? "null" : annots_STTS);
+   	   	}
+
+   	if( DEBUG )
+   		System.out.printf("%s: %s translates to: '%s'.\n", func, annots.toString(), annots_STTS == null ? "null" : annots_STTS);
+	   	
+   	return annots_STTS;
+    }
+    
+    /* translate_MORPH_CONNEXOR:
+     * 
+     * - translates a MORPH expression to a STTS annotation,
+     *   i.e. translates vom C2-style to STTS-style.
+     * - e.g. "VRB fin a"  -> VAFIN.
+     * - e.g. "VRB fin -a" -> V.FIN -VAFIN.
+     * - e.g. "VRB -fin a" -> V.* - F.FIN.
+     * - e.g. "-VRB fin a" -> -V.*.
+     * - tag combinations:
+     *   e.g. "N prop" -> "NE"
+     *   e.g. "N -prop" -> "NN"
+     *   
+     * Returns: STTS annotation if all values could be translated, null else.
+     * 16.05.26/FB
+     */
+    
+    private List<String> translate_MORPH_CONNEXOR(Tree node)
+    
+    {
+    	final String 
+    		func = "translate_MORPH_CONNEXOR";
+    	int
+     		nChildren = node.getChildCount();
+	    List<String>
+	     	newAnnots = new ArrayList<String>();
+	    String 
+	     	newAnnot;
+	    int
+	    	iSkip = 0; // default: skip no children.
+	    boolean
+	    	hasUnknownValues = false;
+	    
+	    // 0. rearrange order of children inside node: 
+	    rearrangeChildren(node, nChildren);
+	    
+	    // 1. try combinations of the first 2 or 3 values including negative prefix.
+	    //    iSkip returns the no. of consumed values, may be 0 if none matches.
+	    //    the translated combination is added to newAnnots:
+	    iSkip = combineAnnots(node, newAnnots);
+	    
+	    // 2. try to translate single values and/or the remaining values after combination has been found (iSkip>0):
+	    //    break at the first value that cannot be translated.
+    	for(int i=iSkip; i<nChildren; i++)
+    		{
+    		String 
+    			child = node.getChild(i).toString();
+    		boolean
+    			isNeg = false;
+    		
+    		if( child.startsWith("-") )
+				{
+    			isNeg = true;
+    			child = child.substring(1);
+				}
+    		
+    		if( i==0 )
+    			newAnnot = MORPH_Mapper.translate_CONNEXOR(child.toUpperCase());
+    		else
+    			newAnnot = MORPH_Mapper.translate_CONNEXOR(child.toLowerCase());
+    		
+    		// add translated annotation if found, else break.
+    		if( newAnnot == null )
+    			{
+    			hasUnknownValues = true;
+    			break; // cannot convert CONNEXOR annotation(s) fully.
+    			}
+    		
+    		// convert to negative value
+    		if( isNeg )
+    			newAnnot = newAnnot.replace("=", "!=");
+    		
+    		newAnnots.add(newAnnot);	
+    		}
+    	
+    	if( DEBUG )
+    		System.out.printf("%s: node='%s' translates to '%s' unknown=%b.\n", func, node.toStringTree(), 
+    							newAnnots != null ? newAnnots.toString() : "null", hasUnknownValues);
+    	
+    	return newAnnots == null || newAnnots.size() == 0 || hasUnknownValues ? null : newAnnots;
+    }
+    
+    /* processOPMORPH: 
+     * - STTS annotation in C2 style are translated to STTS style.
+     * - e.g. "N ne" -> "NE". 
+     * 27.03.26/FB
+     */
+
+    private void processOPMORPH (Tree node) 
+    {
         // Step I: get info
-        String[] morphterms =
-                node.getChild(0).toStringTree().replace(" ", "").split("&");
-        Map<String, Object> token = KoralObjectGenerator.makeToken();
-        ArrayList<Object> terms = new ArrayList<Object>();
-        Map<String, Object> fieldMap = null;
+    	final String
+    		func = "Debug: processOPMORPH";
+        Map<String, Object> 
+        	token = KoralObjectGenerator.makeToken();
+        ArrayList<Object> 
+        	terms = new ArrayList<Object>(); 
+        Map<String, Object> 
+        	fieldMap = null;
+        int	
+        	nChildren = node.getChildCount();
+	    
+        // MORPH empty:
+	    if( nChildren == 1 && node.getChild(0).toString().equals("EMPTY") )
+		    {
+		    addError(StatusCodes.MALFORMED_QUERY, "Empty MORPH() operator."
+                    + " Please specify a valid part of speech (like 'MORPH(NOU)' or 'MORPH(tt/p=NOU)' for searching nouns).");
+            return;
+            }
 
-        for (String morphterm : morphterms) {
-
-            fieldMap = termToFieldMap(morphterm);
-            if (fieldMap == null) {
-                return;
-            };
-
-            terms.add(fieldMap);
-        }
-
-        if (morphterms.length == 1) {
+	    // MORPH with STTS annotations in C2-style:
+	    List <String>
+	    	annots_STTS = null;  
+	    List<String>
+	    	annots_CONNEX = null;
+	    
+	    if( (annots_STTS = translate_MORPH_STTS(node)) != null )
+		    { // translates MORPH expression to STTS expression, considering negative values.
+	    	if( DEBUG )
+	    		System.out.printf("Debug: processOPMORPH: annots_STTS='%s'.\n", annots_STTS == null ? "null" : annots_STTS);
+		    for( String annot : annots_STTS)
+	    		{
+			    fieldMap = termToFieldMap(annot, opMORPH);	
+			    terms.add(fieldMap);
+	    		}
+		    }
+	    else if( (annots_CONNEX=translate_MORPH_CONNEXOR(node)) != null )
+	    	{ // translates MORPH expression of CONNEXOR tagset to marmot pos and morphological expressions, considering negative values.
+	    	if( DEBUG )
+	    		System.out.printf("Debug: processOPMORPH: annots_CONNEX='%s'.\n", annots_CONNEX == null ? "null" : annots_CONNEX);
+		    for( String annot : annots_CONNEX )
+	    		{
+			    fieldMap = termToFieldMap(annot, opMORPH);	
+			    terms.add(fieldMap);
+	    		}
+	    	}
+		else
+		    { // neither STTS nor CONNEXOR:
+			if( DEBUG ) System.out.printf("%s: processing other tags.\n", func);
+		    
+			for(int i=0; i<nChildren; i++)
+		    	{
+		    	String morphterm = node.getChild(i).toString();
+		    	if( DEBUG )
+		    		System.out.printf("Debug: processOPMORPH: morphterm = '%s'.\n", morphterm);
+		    	
+		        fieldMap = termToFieldMap(morphterm, opMORPH);
+		        if (fieldMap == null)
+		        	return;
+			        
+		        terms.add(fieldMap);
+		    	}
+		    }
+	    
+        if (nChildren == 1) 
+        	{
             token.put("wrap", fieldMap);
-        }
-
+        	}
         else {
             Map<String, Object> termGroup = KoralObjectGenerator
                     .makeTermGroup(KoralTermGroupRelation.AND);
             termGroup.put("operands", terms);
             token.put("wrap", termGroup);
         }
+
         // Step II: decide where to put
         putIntoSuperObject(token, 0);
         visited.add(node.getChild(0));
@@ -1246,6 +1915,8 @@
         }
         else {
             // TODO
+        	if( DEBUG )
+        		System.out.printf("Error: processOPWF_OPLEM: TPOS not implemented: '%s'!\n", node.toStringTree());
         }
     }
 
@@ -1352,10 +2023,14 @@
         }
         elem = nodeString.substring(0, 1);
         nodeString = nodeString.substring(1);
+        
+        //System.out.printf("Debug: processPositionCondition: mode='%s' node=='%s' elem='%s'.\n", mode, nodeString, elem);
+
         // in cases where the end of X shall match the beginning of
         // the span, or vice versa,
         // we need to define spanRefs
         if (mode.equals("beg")) {
+        	//System.out.printf("Debug: processPositionCondition: 'beg' nodeString='%s'.\n", nodeString);
             if (nodeString.equals("a")) {
                 position = KoralFrame.STARTS_WITH;
             }
@@ -1365,7 +2040,8 @@
             }
         }
         else if (mode.equals("end")) {
-            if (nodeString.equals("e")) {
+        	//System.out.printf("Debug: processPositionCondition: 'end' nodeString='%s'.\n", nodeString);
+        	if (nodeString.equals("e")) {
                 position = KoralFrame.ENDS_WITH;
             }
             else if (nodeString.equals("a")) {
@@ -1765,11 +2441,17 @@
 
     /**
      * Normalises position operators to equivalents using #BED
+     * 02.06.26/FB
+     * - deactivated as it interacts with other operators.
+     * - e.g. MORPH(temp:past) -> MORPH(#BEG(temp , pa)st) !
+     * 
      */
     private String rewritePositionQuery (String q) {
         Pattern p = Pattern.compile("(\\w+):(([+\\-])?(sa|se|pa|pe|ta|te),?)+");
         Matcher m = p.matcher(q);
 
+        System.out.printf("rewritePositionQuery: enter query='%s'.\n", q);
+        
         String rewrittenQuery = q;
         while (m.find()) {
             String match = m.group();
@@ -1786,35 +2468,50 @@
             replacement = new StringBuilder(replacement.substring(0, replacement.length() - 1) + ")");
             rewrittenQuery = rewrittenQuery.replace(match, replacement.toString());
         }
+        
+        System.out.printf("rewritePositionQuery: leave query='%s'.\n", rewrittenQuery);
         return rewrittenQuery;
     }
 
-
+    // may be replaced by termToFieldMap(term, type).
+    
     private Map<String, Object> termToFieldMap (String term) {
 
         // regex group #2 is foundry, #4 layer, #5 operator,
         // #6 key, #8 value
-        String wordOrRegex = "\\w+|\".+?\"";
+        String wordOrRegex = "\\w+|\".+?\"|'.+?'";
         // TODO: Should be initialized globally
-        Pattern p = Pattern.compile("((\\w+)/)?((\\w*)(!?=))?(" + wordOrRegex
+        Pattern p = Pattern.compile("((\\w+)/)?((\\w*)(!?=|<>))?(" + wordOrRegex
                 + ")(:(" + wordOrRegex + "))?");
         Matcher m;
-
+        boolean negate = false;
+        
         m = p.matcher(term);
         if (!m.matches()) {
             addError(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
                     "Something went wrong parsing the argument in MORPH() or #ELEM().");
             requestMap.put("query", new HashMap<String, Object>());
+            System.out.printf("Debug: termToFieldMap: term='%s' m.matches() = null!\n", term);
             return null;
         };
 
         Map<String, Object> fieldMap = null;
         fieldMap = KoralObjectGenerator.makeTerm();
 
+        if( showRegGroups )	
+        	{
+            for(int j=0; j<= m.groupCount(); j++)
+	        	{
+	        	System.out.printf("Debug: group(%d) = '%s'.\n",  j, 
+	        			m.group(j) != null ? m.group(j) : "null");
+	        	}
+        	}
+        
         if (m.group(2) != null) fieldMap.put("foundry", m.group(2));
         if (m.group(4) != null) fieldMap.put("layer", m.group(4));
         if (m.group(5) != null) {
-            if ("!=".equals(m.group(5))) negate = !negate;
+            if ("!=".equals(m.group(5)) || "<>".equals(m.group(5))) 
+            	negate = !negate;
         }
         if (m.group(6) != null) {
             String key = m.group(6);
@@ -1844,10 +2541,254 @@
         return fieldMap;
     };
 
+    /* isRegExpr():
+     * returns true if str is a regular expression containing
+     * 				'*', *?*, '+' or '.' not being excaped.
+     * 09.06.26/FB
+     */
+    
+    public boolean isRegExpr(String str)
+    
+    {
+    	char c;
+    	
+    	for(int i=0; i<str.length(); i++)
+	    	{
+	    	c = str.charAt(i);
+	    	if( c == '*' || c == '+' || c == '?' || c == '.' || c == '(' || c == ')' || c == '|' )
+	    		{
+	    		if( i == 0 || str.charAt(i-1) != '\\' )
+	    			return true; // wildcard not being escaped.
+	    		}
+    		}
+    	
+    	return false; // not found.
+    }
+   
+   	private void printRegGroups(Matcher m)
+   	
+   	{
+        for(int j=0; j<= m.groupCount(); j++)
+        	{
+        	System.out.printf("Debug: group(%d) = '%s'.\n",  j, 
+        			m.group(j) != null ? m.group(j) : "null");
+        	}
+    }
+   	
+    /* groupToFieldMap:
+     * Args:
+     *   negate: is set in cases like pos!="val val..." or pos<>='val val ..'.
+     * - builds a list of operands out of values[].
+     * - accepts >= 1 value(s) in values[].
+     * Returns 1 termGroup if > 1 values, or 1 term if there is 1 single value in values[].
+     * 09.06.26/FB
+     */
+    
+    private Map<String, Object> groupToFieldMap(Map<String, Object> termMap, String[] values, boolean negate)
+    
+    {
+    	final String func = "Debug: groupToFieldMap";
+    	
+    	if( values == null ) // || values.length == 1 )
+    		return termMap;
+    	
+    	String[]
+    		subValues = null;
+    	Map<String, Object> 
+			termGroup = KoralObjectGenerator.makeTermGroup(KoralTermGroupRelation.AND);
+    	ArrayList<Object> 
+    		terms = new ArrayList<Object>();
+    	boolean
+    		isNeg, isNeg1, isNeg2;
+    	
+    	// clear 'type:regex' and 'value' (if set):
+    	termMap.remove("type");	 // must be set individually.
+    	termMap.remove("value"); // must be set individually.
+    	
+    	for(String term : values)
+    		{
+    		if( DEBUG )
+    			System.out.printf("%s: term='%s'.\n", func, term);
+    		
+    		Map<String, Object>
+    			termMap2 = new HashMap<>(termMap);
+    		
+    		isNeg1 = isNeg2 = false;
+    		
+    		if( isRegExpr(term) )
+    			termMap2.put("type", "type:regex");
+    			
+    		subValues = term.trim().split(":");
+    		if( subValues.length > 1 )
+    			{
+    			isNeg1 = addNegVal(termMap2, "key",   subValues[0]);    			
+    			isNeg2 = addNegVal(termMap2, "value", subValues[1]);
+    			}
+    		else
+    			isNeg1 = addNegVal(termMap2, "key",   term);    			
+			
+    		isNeg = negate;
+    		if( isNeg1 || isNeg2 )
+    			isNeg = !negate;
+    		
+    		if( isNeg )
+    			termMap2.put("match", KoralMatchOperator.NOT_EQUALS.toString());
+    		else
+    			termMap2.put("match", KoralMatchOperator.EQUALS.toString());
+    		 
+    		if( DEBUG )
+    			System.out.printf("%s: termMap2='%s'.\n", func, termMap2.toString());
+    		
+    		terms.add(termMap2);
+        	}
+
+    	if( values.length == 1 )
+    		{
+    		// return a single term.
+    		return (Map<String, Object>) terms.get(0);
+    		}
+    	else
+    		{
+    		// return a group:
+    		termGroup.put("operands", terms);
+        	
+        	return termGroup;	
+    		}
+    }
+    
+    /* termToFieldMap(term, type)
+     * type: opMORPH or opELEM
+     * term: may also contain a list of values:
+     *       e.g. foundry/layer="a b c...", foundry/layer='a b c...'.
+     * shall replace termToFieldMap(term).
+     * - wordOrRegex: also accept regexpr when no '...' nor "..." are used.
+     * - in an older version, layer="a b c" the values inside "..." were automatically
+     *   interpreted as reg. expr.
+     * notes:
+     * - pos=numb:sg -> group6 = numb & group8 = sg
+     * - pos='numb:sg' or pos="numb:sg" -> group6 = numb:sg & group8 = null.
+     * - this is due to the reg. expr of pattern p.
+     * 10.06.26/FB
+     */
+ 
+    private Map<String, Object> termToFieldMap (String term, int type)
+    {
+        // regex group #2 is foundry, #4 layer, #5 operator,
+        // #6 key, #8 value
+    	final String func = "Debug: termToFieldMap";
+    	
+        String wordOrRegex = "-?[\\w][\\w|\\*|\\+|\\.|\\?|\\\\|(|)|\\|]*|\".+?\"|'.+?'";
+        // TODO: Should be initialized globally
+        Pattern p = Pattern.compile("((\\w+)/)?((\\w*)(!?=|<>))?(" + wordOrRegex
+                + ")(:(" + wordOrRegex + "))?");
+        Matcher m;
+        boolean negate = false;
+        
+        m = p.matcher(term);
+        
+        if (!m.matches()) {
+            addError(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
+                    "Something went wrong parsing the argument in MORPH() or #ELEM().");
+            requestMap.put("query", new HashMap<String, Object>());
+            System.out.printf("%s: term='%s' m.matches() = null!\n", func, term);
+            return null;
+        };
+
+        Map<String, Object> fieldMap = KoralObjectGenerator.makeTerm();
+
+        if( showRegGroups )
+        	printRegGroups(m);
+        	
+        if (m.group(2) != null) fieldMap.put("foundry", m.group(2));
+        if (m.group(4) != null) 
+        	fieldMap.put("layer", m.group(4));
+        else
+	        { // MORPH(NOU) -> add default layer = LAYER_POS.
+	        if( type == opMORPH )
+	        	fieldMap.put("layer", LAYER_POS);
+	        }
+        
+        if (m.group(5) != null) {
+            if ("!=".equals(m.group(5)) || "<>".equals(m.group(5))) 
+            	negate = !negate;
+        }
+        
+        // group 6: key or -key or 'key' or 'key key...' or "key" or "key key...":
+       
+        String[] 
+        	values = null;
+        
+        if (m.group(6) != null) 
+	     	{
+	        String key = m.group(6);
+	        boolean isNeg1 = false, isNeg2 = false;
+	        
+	        if ( (key.startsWith("\"") && key.endsWith("\"")) || (key.startsWith("'") && key.endsWith("'")) ) 
+	        	{
+	        	// a list of 1 or more expressions/values is handled by groupToFieldMap().
+	        	key 	 = key.substring(1, key.length() - 1);
+	        	// is this a list with one or more values?
+		        values   = key.trim().split("\\s+"); // split by sequences of white spaces.
+		
+		        if( DEBUG )
+		        	System.out.printf("%s: group6 key '%s' : # of values = %d.\n", func, key, values.length);
+		        
+		        return groupToFieldMap(fieldMap, values, negate);  
+		        }
+	        
+        	// treat here an unquoted single expression/value: Either a single key (e.g. pos=key), or
+        	// a key:value pair (e.g. pos=key:value). In the later case 'value' will be handled with group 8.
+        
+	        if( key.startsWith("-") )
+	        	{
+	        	key    = key.substring(1); // remove '-'.
+	        	negate = true;
+	        	}
+	        if( isRegExpr(key) ) 
+	        	fieldMap.put("type", "type:regex");
+		        
+	        isNeg1 = addNegVal(fieldMap, "key", key);
+	        
+	    	if( isNeg1 )
+    			negate = !negate;
+    		}
+	        
+        // m.group(8) is checked if it is a single value, not written between '..' nor "..":
+        if (m.group(8) != null) 
+        	{
+        	String value = m.group(8);
+        	boolean isNeg = false;
+        	
+            if( DEBUG )
+            	System.out.printf("%s: group 8 = '%s'.\n", func, value);
+            
+	        if ( (value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'")) ) 
+	        	value = value.substring(1, value.length()-1); 
+
+	        isNeg = addNegVal(fieldMap, "value", value);
+            
+	        if( isNeg )
+	        	negate = !negate;
+	        
+	        if ( isRegExpr(value)) 
+	        	fieldMap.put("type", "type:regex");
+            }
+
+        // negate field (see above)
+        if (negate) 
+            fieldMap.put("match", KoralMatchOperator.NOT_EQUALS.toString());
+        else
+            fieldMap.put("match", KoralMatchOperator.EQUALS.toString());
+
+        return fieldMap;        			
+    };
 
     private Tree parseCosmasQuery (String query) {
         
-    	query = rewritePositionQuery(query);
+    	// deactivate rewrite...() as it blindly interacts with every part of a query
+    	// which contains e.g. temp:past like in MORPH(temp:past) - 02.06.26/FB
+    	// query = rewritePositionQuery(query);
+    	
         Tree tree = null;
         Antlr3DescriptiveErrorListener errorListener =
                 new Antlr3DescriptiveErrorListener(query);
@@ -1857,17 +2798,22 @@
             org.antlr.runtime.CommonTokenStream tokens =
                     new org.antlr.runtime.CommonTokenStream(lex); // v3
             
-            parser = new c2psParser(tokens);
-           
             // Use custom error reporters
             lex.setErrorReporter(errorListener);
+            
+            if( DEBUG && bShowTokens )
+	        	TokenUtils.printLexerTokens(tokens, "parseCosmasQuery");
+	        	
+            parser = new c2psParser(tokens);
+           
             ((c2psParser) parser).setErrorReporter(errorListener);
+            
             c2psParser.c2ps_query_return c2Return =
                     ((c2psParser) parser).c2ps_query(); // statt t().
 
             // AST Tree anzeigen:
             tree = (Tree) c2Return.getTree();
-
+          
             if (DEBUG) 
             	{
             	System.out.printf("Debug: parseCosmasQuery: tree = '%s'.\n", tree.toStringTree());
@@ -1890,8 +2836,8 @@
         }
 
         String treestring = tree.toStringTree();
-        
         boolean erroneous = false;
+        
         if (parser.failed() || parser.getNumberOfSyntaxErrors() > 0) {
             erroneous = true;
             tree = null;
@@ -1904,7 +2850,27 @@
         	//System.err.printf("Debug: parseCosmasQuery: tree: '%s'.\n", treestring);
         	//System.err.printf("Debug: parseCosmasQuery: FullErrorMsg:  '%s'.\n", errorListener.generateFullErrorMsg().toString());
         	log.error(errorListener.generateFullErrorMsg().toString());
-            addError(errorListener.generateFullErrorMsg());
+        	if( treestring.contains("<unexpected: [") )
+				{
+	         	Pattern p = Pattern.compile("\\[@\\d+,(\\d+):(\\d+)=");
+	         	Matcher m = p.matcher(treestring);
+	
+	         	if (m.find()) 
+	         		{
+	         	    int start = Integer.parseInt(m.group(1));
+	         	    int end   = Integer.parseInt(m.group(2));
+	
+	         	    if( DEBUG )
+	         	    	System.out.printf("parseCosmasQuery: unexpected input [%d-%d]!\n", start, end);
+	         	    addError(StatusCodes.MALFORMED_QUERY,
+	                         "unexpected input at position " + start + "-" + end + "!");
+	 				}
+	         	else
+	         		addError(errorListener.generateFullErrorMsg());
+				}
+        	else
+        		addError(errorListener.generateFullErrorMsg());
+            return null;
         }
 
         // collect and report errors found by other functions than the lexer/parser:
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 658eb35..21f0f54 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
@@ -184,6 +184,7 @@
         }
         
 		System.out.println(this.toJSON());
+		
     }
 
     public QuerySerializer setQuery (String query, String ql, String version) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/MORPH_Mapper.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/MORPH_Mapper.java
new file mode 100644
index 0000000..38a8f54
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/MORPH_Mapper.java
@@ -0,0 +1,299 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * MORPH_Mapper:
+ * - resource for translating C2-style CONNEXOR tags to marmot annotations 
+ *   and C2-style STTS tags to original STTS annotations as used in KorAP.
+ * 15.06.26/FB
+ */
+
+/**
+ * @author Franck Bodmer
+ * @date 15.06.26
+ */
+public class MORPH_Mapper {
+
+	/* Conversion from C2-style STTS to original STTS tagset.
+	 * C2 style: map represents an annotation hierarchy:
+	 * e.g. VRB > fin > m or a or v.
+	 * input: 1st value = Word class in upper case.
+	 *        following values are in lower case.
+	 * output: all STTS values are in upper case.
+	 */
+	
+    private static final Map<List<String>, String> stts = new HashMap<>();
+    
+	private static final boolean DEBUG = false;
+
+    static {
+        stts.put(List.of("N", "nn"), 				"NN");
+        stts.put(List.of("N", "ne"), 				"NE");
+        stts.put(List.of("N"), 						"N.*");
+        
+        stts.put(List.of("ADJ", "at"),				"ADJA");
+        stts.put(List.of("ADJ", "d"), 				"ADJD");
+        stts.put(List.of("ADJ"), 					"ADJ.");
+        
+        stts.put(List.of("CARD"), 					"CARD");
+        
+        stts.put(List.of("VRB", "fin", "m"),		"VMFIN");
+        stts.put(List.of("VRB", "fin", "a"),		"VAFIN");
+        stts.put(List.of("VRB", "fin", "v"),		"VVFIN");
+        stts.put(List.of("VRB", "fin"),				"V.FIN");
+        stts.put(List.of("VRB", "imp", "a"),		"VAIMP");
+        stts.put(List.of("VRB", "imp", "v"),		"VVIMP");
+        stts.put(List.of("VRB", "imp"),				"V.IMP");
+        
+        stts.put(List.of("VRB", "inf", "a"),		"VAINF");
+        stts.put(List.of("VRB", "inf", "v"),		"VVINF");
+        stts.put(List.of("VRB", "inf", "m"),		"VMINF");
+        stts.put(List.of("VRB", "inf", "z"),		"VVIZU");
+        stts.put(List.of("VRB", "inf"),				"\"(V.INF|VVIZU)\"");   // must be returned between "..." because of the () inside, 
+        																	// or wordOrRegex in TermToFieldMap will fail.
+        stts.put(List.of("VRB", "pp", "a"),			"VAPP");
+        stts.put(List.of("VRB", "pp", "m"),			"VMPP");
+        stts.put(List.of("VRB", "pp", "v"),			"VVPP");
+        stts.put(List.of("VRB", "pp"),				"V.PP");
+
+        stts.put(List.of("VRB"),					"V.*");
+        
+        stts.put(List.of("ART"),					"ART");
+
+        stts.put(List.of("PRON","per", "ref"),		"PRF");
+        stts.put(List.of("PRON","per", "irr"),		"PPER");
+        stts.put(List.of("PRON","per"),				"\"(PRF|PPER)\"");	// reg. expression must be returned with "...".
+
+        stts.put(List.of("PRON","pos", "at"),		"PPOSAT");
+        stts.put(List.of("PRON","pos", "sub"),		"PPOSS");
+        stts.put(List.of("PRON","pos"),				"PPOS.*");
+
+        stts.put(List.of("PRON","dem", "at"),		"PDAT");
+        stts.put(List.of("PRON","dem", "sub"),		"PDS");
+        stts.put(List.of("PRON","dem"),				"PP.*");
+
+        stts.put(List.of("PRON","ind", "at"),		"PIAT");
+        stts.put(List.of("PRON","ind", "atd"),		"PIDAT");
+        stts.put(List.of("PRON","ind", "sub"),		"PIS");
+        stts.put(List.of("PRON","ind"),				"PI.*");
+    
+        stts.put(List.of("PRON","rel", "at"),		"PRELAT");
+        stts.put(List.of("PRON","rel", "sub"),		"PRELS");
+        stts.put(List.of("PRON","rel"),				"PREL.*");
+
+        stts.put(List.of("PRON","w", "at"),			"PWAT");
+        stts.put(List.of("PRON","w", "sub"),		"PWS");
+        stts.put(List.of("PRON","w", "av"),			"PWAV");
+        stts.put(List.of("PRON","w"),				"PW.*");
+
+        stts.put(List.of("PRON","ad"),				"PROAV");
+
+        stts.put(List.of("PRON"),					"P.*");
+
+        stts.put(List.of("ADV"),					"ADV");
+        
+        stts.put(List.of("KON", "unt", "i"),		"KOUI");
+        stts.put(List.of("KON", "unt", "s"),		"KOUS");
+        stts.put(List.of("KON", "neb"),				"KON");
+        stts.put(List.of("KON", "kom"),				"KOKOM");
+        stts.put(List.of("KON"),					"KO.*");
+    
+        stts.put(List.of("AP", "pr"),				"APPR");
+        stts.put(List.of("AP", "prart"),			"APPRART");
+        stts.put(List.of("AP", "po"),				"APPO");
+        stts.put(List.of("AP", "zr"),				"APZR");
+        stts.put(List.of("AP"),						"AP.*");
+
+        stts.put(List.of("PTK", "zu"),				"PTKZU");
+        stts.put(List.of("PTK", "neg"),				"PTKNEG");
+        stts.put(List.of("PTK", "vz"),				"PTKVZ");
+        stts.put(List.of("PTK", "ka"),				"PTKA");
+        stts.put(List.of("PTK", "ant"),				"PTKANT");
+        stts.put(List.of("PTK"),					"PTK.*");
+
+        stts.put(List.of("ITJ"),					"ITJ");
+        stts.put(List.of("TRUNC"),					"TRUNC");
+        stts.put(List.of("XY"),						"XY");
+        stts.put(List.of("FM"),						"FM");
+            
+    }
+
+    //private static final int maxEntries = 3; // max. no. of entries in stts(List<String>...).
+    
+    /* trasnslate_STTS
+     * - translates a list of annotations containing no negative values.
+     * - input is C2-style STTS.
+     * - output is original STTS tagset.
+     * - e.g. [VRB, fin, a] -> "VAFIN".
+     *  
+     * 15.06.26/FB
+     */
+    
+    public static String translate_STTS (List<String> annots) 
+    {
+    	if (stts.containsKey(annots))
+            return stts.get(annots);
+        else
+            return null;
+    }
+
+    /* translate_STTS_withNeg()
+     * 
+     * - translates annotations containing negative values.
+     * - e.g. [VRB, fin, -a] -> VRB/fin - VRB/fin/a -> [V.FIN, -VAFIN].
+     * - e.g. [VRB, -fin, a] -> [V.*, -V.FIN].
+     * - e.g. [-VRB, fin, a] -> [-VAFIN]. Or should it be [-V.*] ?
+     * 
+     * Args:
+     * annots : list of annotations. Hierarchie from left to right conform to STTS.
+     *          assuming the negation char has been removed yet.
+     * iNeg   : zero-based index of the first negative element in annots.
+     * Returns: a list of STTS annotations corresponding to the input list,
+     *          or null if no translation is found.
+     * @author Franck Bodmer
+     * @date 16.06.26
+     */
+    
+    public static List<String> translate_STTS_withNeg(List<String> annots, int iNeg)
+    
+    {
+    if( iNeg < 0 || iNeg >= annots.size() )
+    	return null;
+
+    List<String> 
+    	newAnnots = new ArrayList<String>();
+    String
+    	searchAnnot = translate_STTS(annots.subList(0, iNeg)); // includes 0..iNeg-1.
+    
+    if( searchAnnot == null )
+    	return null;
+    
+    newAnnots.add(searchAnnot);
+    
+    searchAnnot = translate_STTS(annots.subList(0, iNeg+1)); // include 0..iNeg.
+    
+    if( searchAnnot == null )
+    	return null;
+    
+    // a negation char must be included inside the "..." of searchAnnot if searchAnnot == \"...\":
+    // e.g. "-" + "\"(A|B)\"" --> "-\"(A|B)\".
+    if( searchAnnot.startsWith("\"") )
+    	newAnnots.add("\"-" + searchAnnot.substring(1));
+    else
+    	newAnnots.add("-" + searchAnnot);
+    
+    if( DEBUG )
+    	System.out.printf("Debug: translate_STTS_withNeg: annots='%s' -> '%s'.\n", annots.toString(), newAnnots.toString());
+    
+    return newAnnots;	
+    }
+
+    /*
+     *  CONNEXOR tagset. Conversion from the C2 style CONNEXOR to marmot tagset.
+     *  
+     *  - first value in CONNEXOR tags is mandatory and expected to be a POS with capital letters.
+     *    It is converted to marmot/p layer (STTS). Again STTS values may be expressed as a regular expresion,
+     *    e.g. "N" -> "N.", "V" -> "V.*".
+     *  - all other values expected to be lower cased and translate to a morphological annot. 
+     *    on the marmot/m layer.
+     *  - most values translate as single value : single val -> single val. E.g. "N" -> "p=N.".
+     *  - some annotation contain a value pair ("N prop"), or a value tripple ("V pcp prog").
+     *  - in such cases, the first value is expected to be the POS.
+     *  - mandatory and optional values are not checked.
+     */
+    
+    private static final Map<String, String> connexor = new HashMap<>();
+
+    static {
+    	connexor.put("N", 			"p=N."); // N = NN+NE.
+    	connexor.put("N prop",		"p=NE"); // N+prop = NE = named entity.
+    	connexor.put("N -prop",		"p=NN"); // N-prop = NN = normal entity.
+    	connexor.put("N abbr",		"p=NE"); // N+abbr = NE = named entity. There is no abbreviation key word in marmot.
+    	connexor.put("N -abbr",		"p=NN"); // N+abbr = NN = normal entity. There is no abbreviation key word in marmot.
+    	connexor.put("pl",			"m=number:pl");
+    	connexor.put("-pl",			"m=number:sg");	// CONNEXOR: sing encoded as -PL
+        connexor.put("imp", 		"m=mood:imper");
+        connexor.put("ind", 		"m=mood:indic");
+        connexor.put("sub", 		"m=mood:subj");
+        connexor.put("A", 			"p=ADJ.");
+        connexor.put("cmp", 		"m=degree:comp");
+        connexor.put("sup", 		"m=degree:sup");
+        connexor.put("pos", 		"m=degree:pos");
+        connexor.put("ADV", 		"p=ADV");
+        connexor.put("DET",			"p=ART");
+        connexor.put("INTERJ",		"p=ITJ");
+        connexor.put("C",			"p=KO.*"); // extention of MORPH().
+        connexor.put("CC",			"p=KON");
+        connexor.put("CS",			"p=KOUI");
+        connexor.put("NUM -ord",	"p=CARD");	// STTS: has no tag for ORD.        
+        connexor.put("PREP",		"p=APPR");
+        connexor.put("PRON",		"p=P.*");        
+        connexor.put("V",			"p=V.*");     
+        connexor.put("V -inf -pcp",	"p=V.FIN");	// CONNEXOR: finite verbs.     
+        connexor.put("V inf",		"p=V.INF");	// CONNEXOR: infinitive verbs; STTS: includes modal and auxiliary infinitives.     
+        connexor.put("V pcp prog",	"p=ADJ.");	// CONNEXOR: progressive participle, Partizip I; STTS=kein Tag direkt, sondern POS=ADJA oder ADJD.     
+        connexor.put("V pcp perf",	"p=V.PP");	// CONNEXOR: perfect participle, Partizip II; STTS: includes modal and auxiliary participle.     
+        connexor.put("pres",		"m=tense:pres");     
+        connexor.put("past",		"m=tense:past");     
+        
+        // extensions: supplying (missing) tags for MORPH corresponding to a morph-Tag in marmoT:
+        
+        connexor.put("acc",			"m=case:acc");     
+        connexor.put("dat",			"m=case:dat");     
+        connexor.put("gen",			"m=case:gen");     
+        connexor.put("nom",			"m=case:nom");
+        connexor.put("fem",			"m=gender:fem");     
+        connexor.put("masc",		"m=gender:masc");     
+        connexor.put("neut",		"m=gender:neut");     
+        connexor.put("1",			"m=person:1");     
+        connexor.put("2",			"m=person:2");     
+        connexor.put("3",			"m=person:3");     
+        connexor.put("sg",			"m=number:sg");	// CONNEXOR: has no explicit tag for sing. Added here.
+        
+        }
+    
+    /* trasnslate_CONNEXOR
+     * - translates a single c2-style CONNEXOR annotation to a marmot annotation.
+     * - input is C2-style CONNEXOR.
+     * - output is a marmot pos or morphological layer annotation.
+     * - e.g. "N"   -> "p=N".
+     * - e.g. "ind" -> "m=mood:indic"
+     *  
+     * 16.06.26/FB
+     */
+    
+    public static String translate_CONNEXOR (String annot) 
+    {
+    	if (connexor.containsKey(annot))
+            return connexor.get(annot);
+        else
+            return null;
+    }
+
+    /* isMorphAnnot
+     * - e.g. annot = "imp" translate to: "m=mood:imper" "m" = morph. annotation -> return true.
+     * - skip leading negation char in annot: e.g. "-imp" -> check "imp".
+     * - morph. annotations are expected to be in lower case.
+     * 22.06.26/FB
+     */
+    
+    public static boolean isMorphAnnot(String annot)
+    
+    {
+    if( annot == null )
+    	return false;
+    
+    String 
+    	annot2 = (annot.startsWith("-") ? annot.substring(1) : annot).toLowerCase(),
+    	translatedAnnot = connexor.get( annot2);
+    
+    if( translatedAnnot == null )
+    	return false;
+    
+    return translatedAnnot.startsWith("m=") ? true : false;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/util/TokenUtils.java b/src/main/java/de/ids_mannheim/korap/util/TokenUtils.java
new file mode 100644
index 0000000..a03381f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/util/TokenUtils.java
@@ -0,0 +1,42 @@
+package de.ids_mannheim.korap.util;
+
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.Token;
+
+/* general String manipulation functions moved
+ * from de.ids_mannheim.de.korap.query.parse.cosmas.c2ps_opREG.java and Cosmas2QueryProcessor.java.
+ * 24.10.23/FB
+ */
+
+public class TokenUtils {
+
+	//private static final boolean showDebug = false;
+	
+	/**
+	 * printLexerTokens
+	 * - prints the tokens recognized by a lexer. 
+	 * 11.06.26/FB
+	 */
+	   
+	public static void printLexerTokens(CommonTokenStream tokens, String prefix )
+	
+	{
+	if( tokens == null )
+		{
+		System.out.printf("%s: Tokens: null (empty)!\n", prefix);
+		return;
+		}
+	
+	tokens.fill();
+	
+	if( tokens.size() == 0 )
+		{
+		System.out.printf("%s: Tokens: empty!\n", prefix);
+		return;
+		}
+	
+	for( Token token : tokens.getTokens())
+		System.out.printf("%s: Token: '%s'.\n", prefix, token.toString());
+    }
+	
+}
diff --git a/src/test/java/de/ids_mannheim/korap/test/cosmas2/Cosmas2QueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/test/cosmas2/Cosmas2QueryProcessorTest.java
index 6e41921..1ee0aa2 100644
--- a/src/test/java/de/ids_mannheim/korap/test/cosmas2/Cosmas2QueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/test/cosmas2/Cosmas2QueryProcessorTest.java
@@ -5,7 +5,6 @@
 
 import org.junit.Test;
 
-import com.fasterxml.jackson.core.JsonPointer;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -29,21 +28,26 @@
 public class Cosmas2QueryProcessorTest {
 
 
-    String query;
+	// default layer for part of speech:
+    private static final String LAYER_POS = "p";
+    private static final boolean showParserErrorQuery = true;	// print a line around those queries which get "no viable alternative" Parser warnings: 
+    															// the parser warnings are intentional to test syntax errors.
+    															// the parser error for "Der:sa" is corrected in another issue.
+	String query;
     ArrayList<JsonNode> operands;
 
     QuerySerializer qs = new QuerySerializer(1.1);
     ObjectMapper mapper = new ObjectMapper();
     JsonNode res;
 
-
     @Test
     public void testContext () throws JsonProcessingException, IOException {
         String contextString = "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld";
+
         query = "foo";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals(contextString, res.get("@context").asText());
+        assertEquals(contextString, res.get("@context").asText());        
     }
 
 
@@ -57,7 +61,7 @@
         assertEquals("der", res.at("/query/wrap/key").asText());
         assertEquals("orth", res.at("/query/wrap/layer").asText());
         assertEquals("match:eq", res.at("/query/wrap/match").asText());
-
+        
         query = "&Mann";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -134,7 +138,7 @@
         query = "*de+?r";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals(".*de.?.r", res.at("/query/wrap/key").asText());
+        assertEquals(".*de.?.r", res.at("/query/wrap/key").asText());        
     }
 
 
@@ -171,71 +175,536 @@
 
     @Test
     public void testMORPH () throws JsonProcessingException, IOException {
-        query = "MORPH(p=V)";
-        qs.setQuery(query, "cosmas2");
-        res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:token", res.at("/query/@type").asText());
-        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
-        assertEquals("V", res.at("/query/wrap/key").asText());
-        assertEquals("p", res.at("/query/wrap/layer").asText());
-        assertEquals("match:eq", res.at("/query/wrap/match").asText());
 
-        query = "MORPH(V)";
+    	/*
+    	 *  MORPH( expr ) are translated from c2-style STTS to original STTS,
+    	 *    e.g. MORPH(VRB fin a) -> VAFIN.
+    	 */
+    	
+        query = "MORPH(VRB)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        
         assertEquals("koral:token", res.at("/query/@type").asText());
-        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
-        assertEquals("V", res.at("/query/wrap/key").asText());
-        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("V.*", 		res.at("/query/wrap/key").asText());
 
-        query = "MORPH(tt/p=V)";
+        query = "MORPH(VRB fin a)";
         qs.setQuery(query, "cosmas2");
         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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("VAFIN", 				res.at("/query/wrap/operands/0/key").asText());
+        
+        // STTS: although imperative verbs are finit verb, STTS has another tag for it:
+        
+        query = "MORPH(VRB imp)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.IMP", 				res.at("/query/wrap/operands/0/key").asText());
+
+        query = "MORPH(VRB imp a)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("VAIMP", 				res.at("/query/wrap/operands/0/key").asText());
+
+        // imperative forms are negated, "a" is skipped.
+        
+        query = "MORPH(VRB -imp a)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.*", 				res.at("/query/wrap/operands/0/key").asText());
+
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/1/type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("V.IMP", 				res.at("/query/wrap/operands/1/key").asText());
+
+        // STTS: non finit verb, "-a" is skipped:
+        
+        query = "MORPH(VRB -fin -a)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.*",	 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/1/type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p",					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("V.FIN",				res.at("/query/wrap/operands/1/key").asText());
+
+        query = "MORPH(PRON)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", 		res.at("/query/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/layer").asText());
+        assertEquals("P.*",	 				res.at("/query/wrap/key").asText());
+
+        // some C2-style annotations may translate to a reg. expression formulating alternatives:
+        
+        query = "MORPH(PRON per)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("(PRF|PPER)",			res.at("/query/wrap/operands/0/key").asText());
+        
+        query = "MORPH(PRON -per)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("P.*",					res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/1/type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("(PRF|PPER)",			res.at("/query/wrap/operands/1/key").asText());
+
+        query = "MORPH(PRON w -at)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("PW.*",				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("PWAT",				res.at("/query/wrap/operands/1/key").asText());
+
+        /*
+         * CONNEXOR: translating from C2-style annotations to marmot POS + morph:
+         */
+
+        // CONNEXOR: special encoding of C2/CONNEXOR for finite verbs:
+
+        query = "MORPH(V -inf -pcp)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.FIN", 				res.at("/query/wrap/operands/0/key").asText());
+
+        // C2-MORPH-Assistant generates tags like 'imp' in between sequences like "V -inf -pcp",
+        // Serialization must detect this to ensure "V -inf -pcp" is correctly translated.
+        // Also see translation onto 2 different layers:
+        
+        query = "MORPH(V imp -inf -pcp)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.FIN", 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("mood", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("imper", 				res.at("/query/wrap/operands/1/value").asText());
+
+        query = "MORPH(V -imp -inf -past -pcp)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.FIN", 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("mood", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("imper", 				res.at("/query/wrap/operands/1/value").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/2/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/2/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/2/layer").asText());
+        assertEquals("tense", 				res.at("/query/wrap/operands/2/key").asText());
+        assertEquals("past", 				res.at("/query/wrap/operands/2/value").asText());
+
+        // special case for cardinals:
+        
+        query = "MORPH(NUM -ord)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("CARD", 				res.at("/query/wrap/operands/0/key").asText());
+        
+        // MORPH(N -prop) -> p=NE (= normal entity):
+        // order of values must be rearranged:
+        
+        query = "MORPH(N -pl -prop)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("NN",	 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("number", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("pl",	 				res.at("/query/wrap/operands/1/value").asText());
+        
+        // special tagset case: translate from V to ADJ.
+        
+        query = "MORPH(V pcp prog)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("ADJ.", 				res.at("/query/wrap/operands/0/key").asText());
+      
+        query = "MORPH(V pcp prog)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("ADJ.", 				res.at("/query/wrap/operands/0/key").asText());
+      
+        // Extension of C2-Connexor tagset for morph. annotations available in marmot/m:
+        
+        query = "MORPH(N dat sg 1 -masc)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("N.", 					res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("case", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("dat", 				res.at("/query/wrap/operands/1/value").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/2/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/2/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/2/layer").asText());
+        assertEquals("number", 				res.at("/query/wrap/operands/2/key").asText());
+        assertEquals("sg", 					res.at("/query/wrap/operands/2/value").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/3/@type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/3/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/3/layer").asText());
+        assertEquals("person", 				res.at("/query/wrap/operands/3/key").asText());
+        assertEquals("1", 					res.at("/query/wrap/operands/3/value").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/4/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/4/match").asText());
+        assertEquals("m", 					res.at("/query/wrap/operands/4/layer").asText());
+        assertEquals("gender",				res.at("/query/wrap/operands/4/key").asText());
+        assertEquals("masc", 				res.at("/query/wrap/operands/4/value").asText());
+        
+        /*
+         *  C2 Extensions (foundry and layer may be used in MORPH for KorAP):
+         */
+        
+        query = "MORPH(p=VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
         assertEquals("koral:token", res.at("/query/@type").asText());
-        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
-        assertEquals("V", res.at("/query/wrap/key").asText());
-        assertEquals("p", res.at("/query/wrap/layer").asText());
-        assertEquals("tt", res.at("/query/wrap/foundry").asText());
-        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 		res.at("/query/wrap/key").asText());
+        
+        query = "MORPH(p=-VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", 		res.at("/query/@type").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 				res.at("/query/wrap/key").asText());
+        
+        query = "MORPH(p!=VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", 		res.at("/query/@type").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 				res.at("/query/wrap/key").asText());
+
+        query = "MORPH(p<>VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", 		res.at("/query/@type").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/@type").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 				res.at("/query/wrap/key").asText());
+
+        query = "MORPH(tt/p=VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 		res.at("/query/wrap/key").asText());
+
+        query = "MORPH(tt/p=-VRB)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("match:ne", 	res.at("/query/wrap/match").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("VRB", 		res.at("/query/wrap/key").asText());
 
         query = "MORPH(tt/p=\"V.*\")";
         qs.setQuery(query, "cosmas2");
         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("V.*", res.at("/query/wrap/key").asText());
-        assertEquals("p", res.at("/query/wrap/layer").asText());
-        assertEquals("tt", res.at("/query/wrap/foundry").asText());
-        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("type:regex", 	res.at("/query/wrap/type").asText());
+        assertEquals("V.*", 		res.at("/query/wrap/key").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("tt", 			res.at("/query/wrap/foundry").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+
+        // regex also allowed between '...':
+        query = "MORPH(tt/p='V.*')";
+        qs.setQuery(query, "cosmas2");
+        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("V.*", 		res.at("/query/wrap/key").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("tt", 			res.at("/query/wrap/foundry").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+
+        // regex also without any quotes:
+        query = "MORPH(tt/p=V.*)";
+        qs.setQuery(query, "cosmas2");
+        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("V.*", 		res.at("/query/wrap/key").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("tt", 			res.at("/query/wrap/foundry").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+
+        // escaping regex :
+        query = "MORPH(tt/p='V\\.\\*')";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", res.at("/query/@type").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertTrue  (			 	res.at("/query/wrap/type").isMissingNode()); // no type:regex
+        assertEquals("V\\.\\*",		res.at("/query/wrap/key").asText());
+        assertEquals("p", 			res.at("/query/wrap/layer").asText());
+        assertEquals("tt", 			res.at("/query/wrap/foundry").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
+
+        // in this case, the annotation values are translate as is, not via CONNEXOR nor STTS.
+        query = "MORPH(tt/p='V.* -fin -a')";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/0/foundry").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.*",	 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/1/foundry").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("fin", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/2/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/2/foundry").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/2/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/2/layer").asText());
+        assertEquals("a",	 				res.at("/query/wrap/operands/2/key").asText());
+
+        // works also with double quotes, double quotes do not enforce reg. expressions:
+        query = "MORPH(tt/p=\"V.* -fin -a\")";
+        qs.setQuery(query, "cosmas2");
+        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("koral:term", 			res.at("/query/wrap/operands/0/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/0/foundry").asText());
+        assertEquals("type:regex", 			res.at("/query/wrap/operands/0/type").asText());
+        assertEquals("match:eq", 			res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("V.*",	 				res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/1/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/1/foundry").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/1/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("fin", 				res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/operands/2/@type").asText());
+        assertEquals("tt",	 				res.at("/query/wrap/operands/2/foundry").asText());
+        assertEquals("match:ne", 			res.at("/query/wrap/operands/2/match").asText());
+        assertEquals("p", 					res.at("/query/wrap/operands/2/layer").asText());
+        assertEquals("a",	 				res.at("/query/wrap/operands/2/key").asText());
 
         query = "MORPH(mate/m=temp:pres)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        
         assertEquals("koral:token", res.at("/query/@type").asText());
-        assertEquals("koral:term", res.at("/query/wrap/@type").asText());
-        assertEquals("temp", res.at("/query/wrap/key").asText());
-        assertEquals("pres", res.at("/query/wrap/value").asText());
-        assertEquals("m", res.at("/query/wrap/layer").asText());
-        assertEquals("mate", res.at("/query/wrap/foundry").asText());
-        assertEquals("match:eq", res.at("/query/wrap/match").asText());
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("temp", 		res.at("/query/wrap/key").asText());
+        assertEquals("pres", 		res.at("/query/wrap/value").asText());
+        assertEquals("m", 			res.at("/query/wrap/layer").asText());
+        assertEquals("mate", 		res.at("/query/wrap/foundry").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/match").asText());
 
-        query = "MORPH(tt/p=V & mate/m!=temp:pres)";
+        query = "MORPH(tt/p<>V & mate/m!=temp:pres)";
         qs.setQuery(query, "cosmas2");
         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("koral:termGroup", res.at("/query/wrap/@type").asText());
         assertEquals("V", res.at("/query/wrap/operands/0/key").asText());
         assertEquals("p", res.at("/query/wrap/operands/0/layer").asText());
         assertEquals("tt", res.at("/query/wrap/operands/0/foundry").asText());
-        assertEquals("match:eq", res.at("/query/wrap/operands/0/match")
-                .asText());
+        assertEquals("match:ne", res.at("/query/wrap/operands/0/match").asText());
         assertEquals("temp", res.at("/query/wrap/operands/1/key").asText());
         assertEquals("pres", res.at("/query/wrap/operands/1/value").asText());
         assertEquals("m", res.at("/query/wrap/operands/1/layer").asText());
         assertEquals("mate", res.at("/query/wrap/operands/1/foundry").asText());
-        assertEquals("match:ne", res.at("/query/wrap/operands/1/match")
-                .asText());
+        assertEquals("match:ne", res.at("/query/wrap/operands/1/match").asText());
+        
+        // '&' in MORPH is optional: same query without '&' should return same serialization.
+        query = "MORPH(tt/p<>V mate/m!=temp:pres)";
+        qs.setQuery(query, "cosmas2");
+        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("koral:termGroup", res.at("/query/wrap/@type").asText());
+        assertEquals("V", res.at("/query/wrap/operands/0/key").asText());
+        assertEquals("p", res.at("/query/wrap/operands/0/layer").asText());
+        assertEquals("tt", res.at("/query/wrap/operands/0/foundry").asText());
+        assertEquals("match:ne", res.at("/query/wrap/operands/0/match").asText());
+        assertEquals("temp", res.at("/query/wrap/operands/1/key").asText());
+        assertEquals("pres", res.at("/query/wrap/operands/1/value").asText());
+        assertEquals("m", res.at("/query/wrap/operands/1/layer").asText());
+        assertEquals("mate", res.at("/query/wrap/operands/1/foundry").asText());
+        assertEquals("match:ne", res.at("/query/wrap/operands/1/match").asText());
+        
     }
 
 
@@ -278,37 +747,47 @@
         assertEquals("s", res.at("/query/wrap/key").asText());
         assertTrue(res.at("/query/key").isMissingNode());
 		
+        // #ELEM(W) should not generate key=W  - 16.04.26/FB
         query = "der #ELEM(W)";
         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("der", res.at("/query/operands/0/wrap/key").asText());
-        assertEquals("w", res.at("/query/operands/1/wrap/key").asText());
-        assertEquals("koral:span", res.at("/query/operands/1/@type").asText());
-        assertTrue(res.at("/query/operands/2").isMissingNode());
+        assertEquals("koral:group", 		res.at("/query/@type").asText());
+        assertEquals("operation:sequence", 	res.at("/query/operation").asText());
+        assertEquals("koral:token",			res.at("/query/operands/0/@type").asText());
+        assertEquals("koral:term",			res.at("/query/operands/0/wrap/@type").asText());
+        assertEquals("der", 				res.at("/query/operands/0/wrap/key").asText());
+        assertEquals("orth", 				res.at("/query/operands/0/wrap/layer").asText());
 
+        assertEquals("koral:token", 		res.at("/query/operands/1/@type").asText());
+        assertTrue(							res.at("/query/operands/1/layer").isMissingNode());
+
+        // #ELEM(W) should not generate key=W  - 16.04.26/FB
         query = "der #ELEM(W) Mann";
         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("der", res.at("/query/operands/0/wrap/key").asText());
-        assertEquals("w", res.at("/query/operands/1/wrap/key").asText());
-        assertEquals("koral:span", res.at("/query/operands/1/@type").asText());
-        assertEquals("Mann", res.at("/query/operands/2/wrap/key").asText());
-        assertTrue(res.at("/query/operands/3").isMissingNode());
+        assertEquals("koral:group", 		res.at("/query/@type").asText());
+        assertEquals("operation:sequence", 	res.at("/query/operation").asText());
+        assertEquals("koral:token",			res.at("/query/operands/0/@type").asText());
+        assertEquals("der", 				res.at("/query/operands/0/wrap/key").asText());
+        assertEquals("orth", 				res.at("/query/operands/0/wrap/layer").asText());
+        assertEquals("koral:token", 		res.at("/query/operands/1/@type").asText());
+        assertEquals("koral:token",			res.at("/query/operands/2/@type").asText());
+        assertEquals("Mann", 				res.at("/query/operands/2/wrap/key").asText());
+        assertEquals("orth",	 			res.at("/query/operands/2/wrap/layer").asText());
 
         query = "der MORPH(p=ADJA) Mann";
         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("der", res.at("/query/operands/0/wrap/key").asText());
-        assertEquals("ADJA", res.at("/query/operands/1/wrap/key").asText());
-        assertEquals("p", res.at("/query/operands/1/wrap/layer").asText());
-        assertEquals("Mann", res.at("/query/operands/2/wrap/key").asText());
-        assertTrue(res.at("/query/operands/3").isMissingNode());
+        
+        assertEquals("koral:group", 		res.at("/query/@type").asText());
+        assertEquals("operation:sequence", 	res.at("/query/operation").asText());
+        assertEquals("der", 				res.at("/query/operands/0/wrap/key").asText());
+        assertEquals("orth", 				res.at("/query/operands/0/wrap/layer").asText());
+        assertEquals("ADJA", 				res.at("/query/operands/1/wrap/key").asText());
+        assertEquals("p", 					res.at("/query/operands/1/wrap/layer").asText());
+        assertEquals("Mann", 				res.at("/query/operands/2/wrap/key").asText());
+        assertEquals("orth", 				res.at("/query/operands/2/wrap/layer").asText());
+        assertTrue(							res.at("/query/operands/3").isMissingNode());
     }
 
 
@@ -677,14 +1156,6 @@
         qs.setQuery(query,  "cosmas2");
     	res = mapper.readTree(qs.toJSON());
       	
-    	/*
-    	System.out.printf("Query '%s': returns: '%s'.\n", query, res.toPrettyString()) ;
-    	System.out.printf("[0]: '%s'.\n",  res.at("/query/distances").get(0).get("boundary").toPrettyString());
-    	System.out.printf("@type: '%s'.\n",  res.at("/query/distances").get(0).get("@type").asText());
-    	System.out.printf("exclude: '%s'.\n",  res.at("/query/distances").get(0).get("exclude").asText());
-    	System.out.printf("key: '%s'.\n",  res.at("/query/distances").get(0).get("key").asText());
-    	*/
-    	
     	assertEquals("cosmas:distance",  res.at("/query/distances").get(0).get("@type").asText());
     	assertTrue( res.at("/query/distances").get(0).get("exclude").asBoolean());
     	assertEquals("w", res.at("/query/distances").get(0).get("key").asText());
@@ -1058,106 +1529,476 @@
 
     @Test
     public void testELEM () throws JsonProcessingException, IOException {
-        query = "#ELEM(S)";
+    	/* empty query */
+    	query = "#ELEM()";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("s", res.at("/query/wrap/key").asText());
-
-		query = "#ELEM(base/c=NP)";
+        
+       	assertFalse(res.at("/errors").isMissingNode());
+       	assertEquals("302", res.at("/errors/0/0").asText()); // error code for empty #ELEM.;
+         
+    	/** queries which work in C2 **/
+        
+       	query = "#ELEM(S)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("base", res.at("/query/wrap/foundry").asText());
-        assertEquals("c", res.at("/query/wrap/layer").asText());
-        assertEquals("NP", res.at("/query/wrap/key").asText());
-
-        query = "#ELEM(W ANA=N)";
+        
+        assertEquals("koral:span", 	res.at("/query/@type").asText());
+        assertEquals("koral:term",	res.at("/query/wrap/@type").asText());	
+        assertEquals("s",	 		res.at("/query/wrap/key").asText());	// "s" lower cased.
+        assertEquals("s", 			res.at("/query/wrap/layer").asText()); 	// "s" = layer, added - 25.03.26/FB 
+        
+        // corrected: 26.03.26/FB
+        
+        query = "#ELEM(HEAD type=TOP)";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("w", res.at("/query/wrap/key").asText());
+        
+        assertEquals("koral:span", 	res.at("/query/@type").asText());
+        assertEquals("head", 		res.at("/query/wrap/key").asText());	// "head" lower cased.
+        assertEquals("s",    		res.at("/query/wrap/layer").asText());
 		
-        assertEquals("koral:term", res.at("/query/attr/@type").asText());
-        assertEquals("N", res.at("/query/attr/key").asText());
-        assertEquals("p", res.at("/query/attr/layer").asText());
-        assertEquals("match:eq", res.at("/query/attr/match").asText());
-
-        query = "#ELEM(W ANA != 'N V')";
+        assertEquals("koral:term",	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("s", 			res.at("/query/wrap/attr/layer").asText()); // default layer for #ELEM(), same as for element name.
+        assertEquals("type", 		res.at("/query/wrap/attr/key").asText());
+        assertEquals("TOP", 		res.at("/query/wrap/attr/value").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/attr/match").asText());
+        
+        // check several attribute/value pairs and value in single quotes: 27.03.26/FB
+        
+        query = "#ELEM(HEAD type=TOP link=url5 style='bold')";
+        
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("w", res.at("/query/wrap/key").asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/@type").asText());
-        assertEquals("relation:and", res.at("/query/attr/relation").asText());
-        assertEquals("koral:term", res.at("/query/attr/operands/0/@type")
-                .asText());
-        assertEquals("N", res.at("/query/attr/operands/0/key").asText());
-        assertEquals("p", res.at("/query/attr/operands/0/layer").asText());
-        assertEquals("match:ne", res.at("/query/attr/operands/0/match")
-                .asText());
-        assertEquals("koral:term", res.at("/query/attr/operands/1/@type")
-                .asText());
-        assertEquals("V", res.at("/query/attr/operands/1/key").asText());
-        assertEquals("p", res.at("/query/attr/operands/1/layer").asText());
-        assertEquals("match:ne", res.at("/query/attr/operands/1/match")
-                .asText());
+        
+        assertEquals("koral:span", 			res.at("/query/@type").asText());
+        assertEquals("koral:term", 			res.at("/query/wrap/@type").asText());
+        assertEquals("head", 				res.at("/query/wrap/key").asText());
+        assertEquals("s",    				res.at("/query/wrap/layer").asText());
+	
+        assertEquals("koral:termGroup",		res.at("/query/wrap/attr/@type").asText());
+        assertEquals("relation:and",		res.at("/query/wrap/attr/relation").asText());
+        
+        assertEquals("type", 				res.at("/query/wrap/attr/operands/0/key").asText());
+        assertEquals("TOP", 				res.at("/query/wrap/attr/operands/0/value").asText());
+        assertEquals("match:eq",			res.at("/query/wrap/attr/operands/0/match").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/0/@type").asText());
+        assertEquals("s",					res.at("/query/wrap/attr/operands/0/layer").asText());
+        
+        assertEquals("link", 				res.at("/query/wrap/attr/operands/1/key").asText());
+        assertEquals("url5", 				res.at("/query/wrap/attr/operands/1/value").asText());
+        assertEquals("match:eq",			res.at("/query/wrap/attr/operands/1/match").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/1/@type").asText());
+        assertEquals("s",					res.at("/query/wrap/attr/operands/1/layer").asText());
 
-        query = "#ELEM(W ANA != 'N A V' Genre = Sport)";
+        assertEquals("style", 				res.at("/query/wrap/attr/operands/2/key").asText());
+        assertEquals("bold", 				res.at("/query/wrap/attr/operands/2/value").asText());
+        assertEquals("match:eq",			res.at("/query/wrap/attr/operands/2/match").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/2/@type").asText());
+        assertEquals("s",					res.at("/query/wrap/attr/operands/2/layer").asText());
+
+        // check attribute with several values - 27.03.26/FB
+        query = "#ELEM(HEAD type!='url5 top bold')";
+        
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("w", res.at("/query/wrap/key").asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/@type").asText());
-        assertEquals("relation:and", res.at("/query/attr/relation").asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/operands/0/@type")
-                .asText());
-        assertEquals("relation:and", res.at("/query/attr/operands/0/relation")
-                .asText());
-        assertEquals("N", res.at("/query/attr/operands/0/operands/0/key")
-                .asText());
-        assertEquals("A", res.at("/query/attr/operands/0/operands/1/key")
-                .asText());
-        assertEquals("V", res.at("/query/attr/operands/0/operands/2/key")
-                .asText());
-        assertEquals("Genre", res.at("/query/attr/operands/1/layer").asText());
-        assertEquals("Sport", res.at("/query/attr/operands/1/key").asText());
+        
+        assertEquals("koral:span", 			res.at("/query/@type").asText());
+        assertEquals("head", 				res.at("/query/wrap/key").asText());
+        assertEquals("s",    				res.at("/query/wrap/layer").asText());
+		
+        assertEquals("koral:termGroup",		res.at("/query/wrap/attr/@type").asText());
+        assertEquals("relation:and",		res.at("/query/wrap/attr/relation").asText());
+        
+        assertEquals("type", 				res.at("/query/wrap/attr/operands/0/key").asText());
+        assertEquals("url5", 				res.at("/query/wrap/attr/operands/0/value").asText());
+        assertEquals("match:ne",			res.at("/query/wrap/attr/operands/0/match").asText());
+        assertEquals("s",	 				res.at("/query/wrap/attr/operands/0/layer").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/0/@type").asText());
+        
+        assertEquals("type", 				res.at("/query/wrap/attr/operands/1/key").asText());
+        assertEquals("top", 				res.at("/query/wrap/attr/operands/1/value").asText());
+        assertEquals("match:ne",			res.at("/query/wrap/attr/operands/1/match").asText());
+        assertEquals("s",	 				res.at("/query/wrap/attr/operands/1/layer").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/1/@type").asText());
 
-        query = "#ELEM(W ANA != 'N A V' Genre != 'Sport Politik')";
+        assertEquals("type", 				res.at("/query/wrap/attr/operands/2/key").asText());
+        assertEquals("bold", 				res.at("/query/wrap/attr/operands/2/value").asText());
+        assertEquals("match:ne",			res.at("/query/wrap/attr/operands/2/match").asText());
+        assertEquals("s",	 				res.at("/query/wrap/attr/operands/2/layer").asText());
+        assertEquals("koral:term",			res.at("/query/wrap/attr/operands/2/@type").asText());
+        
+        // check attr <> value - 27.03.26/FB
+        
+        query = "#ELEM(HEAD style<>bold)";
+        
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
-        assertEquals("koral:span", res.at("/query/@type").asText());
-        assertEquals("w", res.at("/query/wrap/key").asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/@type").asText());
-        assertEquals("relation:and", res.at("/query/attr/relation").asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/operands/0/@type")
-                .asText());
-        assertEquals("relation:and", res.at("/query/attr/operands/0/relation")
-                .asText());
-        assertEquals("koral:termGroup", res.at("/query/attr/operands/1/@type")
-                .asText());
-        assertEquals("relation:and", res.at("/query/attr/operands/1/relation")
-                .asText());
-        assertEquals("N", res.at("/query/attr/operands/0/operands/0/key")
-                .asText());
-        assertEquals("A", res.at("/query/attr/operands/0/operands/1/key")
-                .asText());
-        assertEquals("V", res.at("/query/attr/operands/0/operands/2/key")
-                .asText());
-        assertEquals("match:ne",
-                res.at("/query/attr/operands/0/operands/2/match").asText());
-        assertEquals("Genre", res.at("/query/attr/operands/1/operands/0/layer")
-                .asText());
-        assertEquals("Sport", res.at("/query/attr/operands/1/operands/0/key")
-                .asText());
-        assertEquals("Genre", res.at("/query/attr/operands/1/operands/1/layer")
-                .asText());
-        assertEquals("Politik", res.at("/query/attr/operands/1/operands/1/key")
-                .asText());
-        assertEquals("match:ne",
-                res.at("/query/attr/operands/1/operands/1/match").asText());
+        
+        assertEquals("koral:span", 	res.at("/query/@type").asText());
+        assertEquals("head", 		res.at("/query/wrap/key").asText());
+        assertEquals("s",    		res.at("/query/wrap/layer").asText());
+		
+        assertEquals("koral:term",	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("s", 			res.at("/query/wrap/attr/layer").asText()); // default layer for #ELEM(), same as for element name.
+        assertEquals("style", 		res.at("/query/wrap/attr/key").asText());
+        assertEquals("bold", 		res.at("/query/wrap/attr/value").asText());
+        assertEquals("match:ne", 	res.at("/query/wrap/attr/match").asText());
+        
+        // no element specified, attribute only:
+        query = "#ELEM(type=TOP)";
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:span", 	res.at("/query/@type").asText());
+	
+        assertEquals("koral:term",	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("s", 			res.at("/query/wrap/attr/layer").asText()); // default layer for #ELEM(), same as for element name.
+        assertEquals("type", 		res.at("/query/wrap/attr/key").asText());
+        assertEquals("TOP", 		res.at("/query/wrap/attr/value").asText());
+        assertEquals("match:eq", 	res.at("/query/wrap/attr/match").asText());
+    }
+    
+    /* a special case of #ELEM is the use of element name 'W' and/or attribute 'ana',
+     * which must be mapped in KorAP onto the layer for part of speech.
+     * 17.04.26/FB
+     */
+    
+    @Test
+    public void testELEM_W() throws JsonProcessingException, IOException 
+    {
+        query = "#ELEM(W ANA=NOU)";
+        
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        // System.out.printf("Query='%s' res='%s'.\n", query, res.toString());
+        
+        assertEquals("koral:token",	res.at("/query/@type").asText());
+        assertFalse("Element 'w' not expected as a key!", 
+        							res.at("/query/wrap/key").asText().equals("w"));
+        assertFalse("Attribute 'ana' not expected as a key!",
+        							res.at("/query/attr/key").asText().equals("ana"));
+        assertEquals("koral:term", 	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("NOU", 		res.at("/query/wrap/attr/key").asText());
+        assertEquals(LAYER_POS, 	res.at("/query/wrap/attr/layer").asText()); // same as element name.
+        assertEquals("match:eq", 	res.at("/query/wrap/attr/match").asText());
+
+        // Element 'W' is optional when using attribute 'ana':
+        
+        query = "#ELEM(ana=NOU)";
+        
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        // System.out.printf("Query='%s' res='%s'.\n", query, res.toString());
+        
+        assertEquals("koral:token",	res.at("/query/@type").asText());
+        assertFalse("Element 'w' not expected as a key!", 
+        							res.at("/query/wrap/key").asText().equals("w"));
+        assertFalse("Attribute 'ana' not expected as a key!",
+        							res.at("/query/wrap/key").asText().equals("ana"));
+        assertEquals("koral:term", 	res.at("/query/wrap/@type").asText());
+        assertEquals("NOU", 		res.at("/query/wrap/attr/key").asText());
+        assertEquals(LAYER_POS, 	res.at("/query/wrap/attr/layer").asText()); // same as element name.
+        assertEquals("match:eq", 	res.at("/query/wrap/attr/match").asText());
+
+        query = "#ELEM(W ANA='NOU SG PROPN')";
+
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token",		res.at("/query/@type").asText());
+        
+        assertEquals("koral:term", 		res.at("/query/wrap/@type").asText());
+        assertEquals(LAYER_POS, 		res.at("/query/wrap/layer").asText());
+        
+        assertEquals("koral:termGroup",	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("relation:and", 	res.at("/query/wrap/attr/relation").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/0/@type").asText());
+        assertEquals("NOU",		 		res.at("/query/wrap/attr/operands/0/key").asText());
+        assertEquals("match:eq",		res.at("/query/wrap/attr/operands/0/match").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/1/@type").asText());
+        assertEquals("SG",		 		res.at("/query/wrap/attr/operands/1/key").asText());
+        assertEquals("match:eq",		res.at("/query/wrap/attr/operands/1/match").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/2/@type").asText());
+        assertEquals("PROPN",	 		res.at("/query/wrap/attr/operands/2/key").asText());
+        assertEquals("match:eq",		res.at("/query/wrap/attr/operands/2/match").asText());
+
+        // for part of speech, 2 kinds of negation are possible:
+        query = "#ELEM(W ana='NOU -PL' ana!='PROPN')";
+
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token", 	res.at("/query/@type").asText());
+        
+        assertEquals("koral:term", 		res.at("/query/wrap/@type").asText());
+        assertEquals(LAYER_POS, 		res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", 		res.at("/query/wrap/match").asText());
+        
+        assertEquals("koral:termGroup",	res.at("/query/wrap/attr/@type").asText());
+        assertEquals("relation:and", 	res.at("/query/wrap/attr/relation").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/0/operands/0/@type").asText());
+        assertEquals("NOU",		 		res.at("/query/wrap/attr/operands/0/operands/0/key").asText());
+        assertEquals("match:eq",		res.at("/query/wrap/attr/operands/0/operands/0/match").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/0/operands/1/@type").asText());
+        assertEquals("PL",		 		res.at("/query/wrap/attr/operands/0/operands/1/key").asText());
+        assertEquals("match:ne",		res.at("/query/wrap/attr/operands/0/operands/1/match").asText());
+        assertEquals("koral:termGroup",	res.at("/query/wrap/attr/operands/0/@type").asText());
+        assertEquals("relation:and",	res.at("/query/wrap/attr/operands/0/relation").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/attr/operands/1/@type").asText());
+        assertEquals("PROPN",	 		res.at("/query/wrap/attr/operands/1/key").asText());
+        assertEquals("match:ne",		res.at("/query/wrap/attr/operands/1/match").asText());
+        
+        // the other operator for negation:
+        query = "#ELEM(W ANA!='NOU')";
+
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token",		res.at("/query/@type").asText());
+        assertEquals("koral:term", 		res.at("/query/wrap/@type").asText());
+        assertEquals(LAYER_POS, 		res.at("/query/wrap/layer").asText());
+        assertEquals("match:eq", 		res.at("/query/wrap/match").asText());
+        
+        assertEquals("koral:term",		res.at("/query/wrap/attr/@type").asText());
+        assertEquals("match:ne",		res.at("/query/wrap/attr/match").asText());
+        assertEquals("NOU",			 	res.at("/query/wrap/attr/key").asText());
+        assertEquals(LAYER_POS,			res.at("/query/wrap/attr/layer").asText());
+        
+        // different attributes: 'ana' and non 'ana':
+        query = "#ELEM(W ANA = 'NOU' type!='COMP')";
+        
+        qs.setQuery(query, "cosmas2");
+        res = mapper.readTree(qs.toJSON());
+        
+        assertEquals("koral:token",		res.at("/query/@type").asText());
+        
+        assertEquals("koral:term", 		res.at("/query/wrap/@type").asText());
+        assertEquals("match:eq", 		res.at("/query/wrap/match").asText());
+        assertEquals(LAYER_POS,		 	res.at("/query/wrap/layer").asText());
+        
+        assertEquals("koral:termGroup", res.at("/query/wrap/attr/@type").asText());
+        assertEquals("relation:and", 	res.at("/query/wrap/attr/relation").asText());
+        
+        assertEquals("koral:term",		res.at("/query/wrap/attr/operands/0/@type").asText());
+        assertEquals("match:eq",	 	res.at("/query/wrap/attr/operands/0/match").asText());
+        assertEquals("NOU",				res.at("/query/wrap/attr/operands/0/key").asText());
+        
+        assertEquals("koral:term",		res.at("/query/wrap/attr/operands/1/@type").asText());
+        assertEquals("match:ne",	 	res.at("/query/wrap/attr/operands/1/match").asText());
+        assertEquals("type",			res.at("/query/wrap/attr/operands/1/key").asText()); 	// normal case 
+        assertEquals("COMP",			res.at("/query/wrap/attr/operands/1/value").asText());	// normal case
+
     }
 
+    /*
+     * Extensions of C2-Queries for KorAP : 
+     * - specify foundry and layer.
+     * - e.g. #ELEM(base/p=NOU).
+     * 20.04.26/FB
+     */
+    
+    @Test
+    public void testELEM_Foundry() throws JsonProcessingException, IOException {
+    	
+    // special case: for "dereko/p" the @type is a term.
+	query = "#ELEM(dereko/s=head)";
+	
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span",	 		res.at("/query/@type").asText());
+    assertEquals("koral:term", 			res.at("/query/wrap/@type").asText());
+    assertEquals("match:eq",		 	res.at("/query/wrap/attr/match").asText());
+    assertEquals("dereko", 				res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("s", 					res.at("/query/wrap/attr/layer").asText());
+    assertEquals("head", 				res.at("/query/wrap/attr/key").asText());
+    
+    /* base or dereko ? */
+    query = "#ELEM(base/c=NP)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("base", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("c", 				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("NP", 				res.at("/query/wrap/attr/key").asText());
+    
+    query = "#ELEM(marmot/m=number:pl)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("marmot", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("m",	 			res.at("/query/wrap/attr/layer").asText());
+    assertEquals("number", 			res.at("/query/wrap/attr/key").asText());
+    assertEquals("pl",	 			res.at("/query/wrap/attr/value").asText());
+    
+    query = "#ELEM(marmot/m='number:pl gender:-fem')";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("marmot", 			res.at("/query/wrap/attr/operands/0/foundry").asText());
+    assertEquals("m",	 			res.at("/query/wrap/attr/operands/0/layer").asText());
+    assertEquals("number", 			res.at("/query/wrap/attr/operands/0/key").asText());
+    assertEquals("pl",	 			res.at("/query/wrap/attr/operands/0/value").asText());
+    
+    assertEquals("marmot", 			res.at("/query/wrap/attr/operands/1/foundry").asText());
+    assertEquals("m",	 			res.at("/query/wrap/attr/operands/1/layer").asText());
+    assertEquals("gender", 			res.at("/query/wrap/attr/operands/1/key").asText());
+    assertEquals("fem",	 			res.at("/query/wrap/attr/operands/1/value").asText());
+    assertEquals("match:ne",		res.at("/query/wrap/attr/operands/1/match").asText());
+    
+    query = "#ELEM(marmot/m!=number:pl)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("marmot", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("m",	 			res.at("/query/wrap/attr/layer").asText());
+    assertEquals("number", 			res.at("/query/wrap/attr/key").asText());
+    assertEquals("pl",	 			res.at("/query/wrap/attr/value").asText());
+    assertEquals("match:ne",		res.at("/query/wrap/attr/match").asText());
+    
+    query = "#ELEM(HEAD dereko/s<>type:top)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("dereko", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("s", 				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("type", 			res.at("/query/wrap/attr/key").asText());
+    assertEquals("top",	 			res.at("/query/wrap/attr/value").asText());
+    assertEquals("match:ne",		res.at("/query/wrap/attr/match").asText());
+
+    query = "#ELEM(HEAD dereko/s='top bottom')";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("s",				res.at("/query/wrap/layer").asText());
+    assertEquals("head",			res.at("/query/wrap/key").asText());
+    assertEquals("koral:termGroup",	res.at("/query/wrap/attr/@type").asText());
+    assertEquals("relation:and",	res.at("/query/wrap/attr/relation").asText());
+    assertEquals("dereko", 			res.at("/query/wrap/attr/operands/0/foundry").asText());
+    assertEquals("s",				res.at("/query/wrap/attr/operands/0/layer").asText());
+    assertEquals("top", 			res.at("/query/wrap/attr/operands/0/key").asText());
+    assertEquals("dereko", 			res.at("/query/wrap/attr/operands/1/foundry").asText());
+    assertEquals("s",				res.at("/query/wrap/attr/operands/1/layer").asText());
+    assertEquals("bottom", 			res.at("/query/wrap/attr/operands/1/key").asText());
+    
+    /*
+     * test the other foundries/layers for either token or span.
+     */
+    
+    query = "#ELEM(tt/l=Haus)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/attr/@type").asText());
+    assertEquals("tt",	 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("l",				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("Haus", 			res.at("/query/wrap/attr/key").asText());
+    
+    query = "#ELEM(base/s=s)"; 		// sentences
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("base", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("s", 				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("s", 				res.at("/query/wrap/attr/key").asText());
+
+    query = "#ELEM(base/s=p)"; 		// paragraphes
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("base", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("s", 				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("p", 				res.at("/query/wrap/attr/key").asText());
+
+    query = "#ELEM(base/s=t)"; 		// texts (?)
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span", 		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("base", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("s", 				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("t", 				res.at("/query/wrap/attr/key").asText());
+
+    query = "#ELEM(marmot/p=APPO)";
+        
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/attr/@type").asText());
+    assertEquals("marmot", 			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("p",				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("APPO", 			res.at("/query/wrap/attr/key").asText());
+    
+    query = "#ELEM(corenlp/p=ADJD)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("corenlp",			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("p",				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("ADJD", 			res.at("/query/wrap/attr/key").asText());
+    
+    query = "#ELEM(corenlp/c=AP)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:span",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/attr/@type").asText());
+    assertEquals("corenlp",			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("c",				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("AP", 				res.at("/query/wrap/attr/key").asText());
+    
+    query = "#ELEM(corenlp/ne=I-ORG)";
+    
+    qs.setQuery(query, "cosmas2");
+    res = mapper.readTree(qs.toJSON());
+    
+    assertEquals("koral:token",		res.at("/query/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/@type").asText());
+    assertEquals("koral:term",		res.at("/query/wrap/attr/@type").asText());
+    assertEquals("corenlp",			res.at("/query/wrap/attr/foundry").asText());
+    assertEquals("ne",				res.at("/query/wrap/attr/layer").asText());
+    assertEquals("I-ORG", 			res.at("/query/wrap/attr/key").asText());
+
+    }
 
     @Test
     public void testOPALL () throws JsonProcessingException, IOException {
@@ -1621,9 +2462,13 @@
     @Test
     public void testColonSeparatedConditions () throws JsonProcessingException,
             IOException {
+    	
         query = "der:sa";
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s'.\n", query);
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s': done.\n", query);
+        
         assertEquals("koral:reference", res.at("/query/@type").asText());
         assertEquals("operation:focus", res.at("/query/operation").asText());
         assertEquals(129, res.at("/query/classRef/0").asInt());
@@ -1647,7 +2492,7 @@
                 .asText());
         assertEquals("s", res.at("/query/operands/0/operands/0/wrap/key")
                 .asText());
-
+/* fails after modifications to MORPH -- 12.06.26/FB
         query = "der:sa,-pa";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -1711,7 +2556,8 @@
                 "der",
                 res.at("/query/operands/0/operands/1/operands/1/operands/0/wrap/key")
                         .asText());
-
+*/
+        /*
         query = "der:sa,-pa,+te";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -1869,6 +2715,7 @@
                 "der",
                 res.at("/query/operands/0/operands/1/operands/0/operands/1/operands/1/operands/0/operands/0/wrap/key")
                         .asText());
+*/
     }
 
     @Test
@@ -1885,32 +2732,39 @@
     @Test
     public void testErrors () throws JsonProcessingException, IOException {
         query = "MORPH(tt/p=\"\")";
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s'.\n", query);
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s': done.\n", query);
         assertTrue(res.at("/query/@type").isMissingNode());
-        assertEquals(StatusCodes.INCOMPATIBLE_OPERATOR_AND_OPERAND,
+        assertEquals(StatusCodes.MALFORMED_QUERY,
                 res.at("/errors/0/0").asInt());
         assertTrue(res
                 .at("/errors/0/1")
                 .asText()
                 .startsWith(
-                        "Something went wrong parsing the argument in MORPH()"));
-        /*
+                        "Empty MORPH() operator"));
+        
         query = "MORPH(tt/p=\"foo)";
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s'.\n", query);
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s': done.\n", query);
         assertTrue(res.at("/query/@type").isMissingNode());
         assertEquals(StatusCodes.MALFORMED_QUERY, res.at("/errors/0/0").asInt());
         assertTrue(res.at("/errors/0/1").asText()
-                .startsWith("Early closing parenthesis"));
-		*/
+                .startsWith("unexpected input at position"));
+		
         query = "MORPH(tt/p=)";
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s'.\n", query);
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
+        if( showParserErrorQuery ) System.err.printf("Debug: intentional warning: query='%s': done.\n", query);
         assertTrue(res.at("/query/@type").isMissingNode());
         assertEquals(StatusCodes.MALFORMED_QUERY, res.at("/errors/0/0").asInt());
         assertTrue(res.at("/errors/0/1").asText()
-                .startsWith("Early closing parenthesis"));
+                .startsWith("unexpected input at position"));
+    	
     }
 
     @Test
@@ -1942,7 +2796,6 @@
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         
-        if( debug ) System.out.printf("testREG: query: >>%s<< -> key: >>%s<<.\n",  query, res.at("/query/wrap/key").asText());
         assertEquals("koral:token", res.at("/query/@type").asText());
         assertEquals("koral:term",  res.at("/query/wrap/@type").asText());
         assertEquals("^aber$",      res.at("/query/wrap/key").asText());
@@ -1954,7 +2807,6 @@
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         
-        if( debug ) System.out.printf("testREG: query: >>%s<< -> key: >>%s<<.\n",  query, res.at("/query/wrap/key").asText());
         assertEquals("été'"	,       res.at("/query/wrap/key").asText());
         assertEquals("type:regex",  res.at("/query/wrap/type").asText());
         assertEquals("orth",        res.at("/query/wrap/layer").asText());
@@ -1963,7 +2815,6 @@
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         
-        if( debug ) System.out.printf("testREG: query: >>%s<< -> key: >>%s<<.\n",  query, res.at("/query/wrap/key").asText());
         assertEquals("été"	,       res.at("/query/wrap/key").asText());
         assertEquals("type:regex",  res.at("/query/wrap/type").asText());
         assertEquals("orth",        res.at("/query/wrap/layer").asText());
@@ -1972,7 +2823,6 @@
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
         
-        if( debug ) System.out.printf("testREG: query: >>%s<< -> key: >>%s<<.\n",  query, res.at("/query/wrap/key").asText());
         assertEquals("été\\",       res.at("/query/wrap/key").asText());
         assertEquals("type:regex",  res.at("/query/wrap/type").asText());
         assertEquals("orth",        res.at("/query/wrap/layer").asText());
@@ -2098,13 +2948,13 @@
         assertEquals("A.*ung",      res.at("/query/operands/1/operands/0/wrap/key").asText());
         assertEquals("orth",        res.at("/query/operands/1/operands/0/wrap/layer").asText());
         assertEquals("type:regex",  res.at("/query/operands/1/operands/0/wrap/type").asText());
- 
+        /*
         query = "#REG( ) ";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
- 
+ 		
         assertTrue(res.toString().contains("Failing to parse"));
-        
+        */
         query = "#REG('' ) ";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -2147,8 +2997,7 @@
         sb = new StringBuffer("   abc");
         StringUtils.removeBlanksAtBothSides(sb);
         assertEquals("abc",sb.toString());
-    }
-    
-  
-    
+
+        }
+        
 }
diff --git a/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINTest.java b/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINTest.java
index 5e42a75..e7be629 100644
--- a/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINTest.java
+++ b/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINTest.java
@@ -70,6 +70,7 @@
     
     @Test
     public void testOPIN () throws JsonProcessingException, IOException {
+    	
         query = "wegen #IN <s>";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -343,7 +344,8 @@
     @Ignore
     public void testOPINWithOptionN_MAX ()
             throws JsonProcessingException, IOException {
-        // EM: Fix operation:merge
+ 	
+    	// EM: Fix operation:merge
         query = "wegen #IN(N, MAX) <s>";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -356,6 +358,7 @@
                 res.at("/query/operands/0/operation").asText());
         assertEquals("frames:isAround",
                 res.at("/query/operands/0/frames/0").asText());
+
     }
     
 }
diff --git a/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINWithExclusionTest.java b/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINWithExclusionTest.java
index 9369100..97c4034 100644
--- a/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINWithExclusionTest.java
+++ b/src/test/java/de/ids_mannheim/korap/test/cosmas2/OPINWithExclusionTest.java
@@ -25,6 +25,7 @@
     @Test
     public void testOPINWithExclusion ()
             throws JsonProcessingException, IOException {
+
         query = "wegen #IN(%) <s>";
         qs.setQuery(query, "cosmas2");
         res = mapper.readTree(qs.toJSON());
@@ -336,6 +337,7 @@
         
         assertEquals("operation:class", seqOperand.at("/1/operation").asText());
         assertEquals(129, seqOperand.at("/1/classOut").asInt());
+
     }
-    
+
 }