poliarp span attributes
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
index b989749..046f625 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/PoliqarpPlusTree.java
@@ -188,11 +188,17 @@
 		}
 
 		if (nodeCat.equals("span")) {
+			List<ParseTree> negations = getChildrenWithCat(node, "!");
+			boolean negated = false;
+			boolean isRegex = false;
+			if (negations.size() % 2 == 1) negated = true;
 			LinkedHashMap<String,Object> span = makeSpan();
 			ParseTree keyNode = getFirstChildWithCat(node, "key");
 			ParseTree layerNode = getFirstChildWithCat(node, "layer");
 			ParseTree foundryNode = getFirstChildWithCat(node, "foundry");
 			ParseTree termOpNode = getFirstChildWithCat(node, "termOp");
+			ParseTree termNode = getFirstChildWithCat(node, "term");
+			ParseTree termGroupNode = getFirstChildWithCat(node, "termGroup");
 			if (foundryNode != null) span.put("foundry", foundryNode.getText());
 			if (layerNode != null) {
 				String layer = layerNode.getText();
@@ -202,8 +208,16 @@
 			span.put("key", keyNode.getText());
 			if (termOpNode != null) {
 				String termOp = termOpNode.getText();
-				if (termOp.equals("==")) span.put("matches", "matches:eq");
-				else if (termOp.equals("!=")) span.put("matches", "matches:ne");
+				if (termOp.equals("==")) span.put("match", "match:eq");
+				else if (termOp.equals("!=")) span.put("match", "match:ne");
+			}
+			if (termNode != null) {
+				LinkedHashMap<String,Object> termOrTermGroup = parseTermOrTermGroup(termNode, negated, "span");
+				span.put("attr", termOrTermGroup);
+			}
+			if (termGroupNode != null) {
+				LinkedHashMap<String,Object> termOrTermGroup = parseTermOrTermGroup(termGroupNode, negated, "span");
+				span.put("attr", termOrTermGroup);
 			}
 			putIntoSuperObject(span);
 			objectStack.push(span);
@@ -373,6 +387,12 @@
 		return node.toStringTree(parser);
 	}
 
+
+	private LinkedHashMap<String, Object> parseTermOrTermGroup(
+			ParseTree node, boolean negated) {
+		return parseTermOrTermGroup(node, negated, "token");
+	}
+	
 	/**
 	 * Parses a (term) or (termGroup) node
 	 * @param node
@@ -381,7 +401,7 @@
 	 * @return A term or termGroup object, depending on input
 	 */
 	@SuppressWarnings("unchecked")
-	private LinkedHashMap<String, Object> parseTermOrTermGroup(ParseTree node, boolean negatedGlobal) {
+	private LinkedHashMap<String, Object> parseTermOrTermGroup(ParseTree node, boolean negatedGlobal, String mode) {
 		if (getNodeCat(node).equals("term")) {
 			String key = null;
 			LinkedHashMap<String,Object> term = makeTerm();
@@ -403,7 +423,8 @@
 			if (layerNode != null) {
 				String layer = layerNode.getText();
 				if (layer.equals("base")) layer="lemma";
-				term.put("layer", layer);
+				if (mode.equals("span")) term.put("key", layer);
+				else term.put("layer", layer);
 			}
 			// process key: 'normal' or regex?
 			key = keyNode.getText();
@@ -412,7 +433,8 @@
 				term.put("type", "type:regex");
 				key = key.substring(1, key.length()-1); // remove leading and trailing quotes
 			}
-			term.put("key", key);
+			if (mode.equals("span")) term.put("value", key);
+			else term.put("key", key);
 			// process value
 			if (valueNode != null) term.put("value", valueNode.getText());
 			// process operator ("match" property)
@@ -453,8 +475,8 @@
 			termGroup = makeTermGroup(operator);
 			ArrayList<Object> operands = (ArrayList<Object>) termGroup.get("operands");
 			// recursion with left/right operands
-			operands.add(parseTermOrTermGroup(leftOp, negatedGlobal));
-			operands.add(parseTermOrTermGroup(rightOp, negatedGlobal));
+			operands.add(parseTermOrTermGroup(leftOp, negatedGlobal, mode));
+			operands.add(parseTermOrTermGroup(rightOp, negatedGlobal, mode));
 			return termGroup;
 		}
 	}
@@ -595,7 +617,9 @@
 				"[orth=Mann][][orth=Mann]",
 				"startswith(<s>, [][base=Mann])",
 				"[base=der][]{1,102}[base=Mann]",
-				"[base=geht][base=der][]*[base=Mann]"
+				"[base=geht][base=der][]*[base=Mann]",
+				"<cnx/c=vp (class=header&id=7)>",
+				"<cnx/c=vp class=header&id=a>"
 		};
 //		PoliqarpPlusTree.verbose=true;
 		for (String q : queries) {
diff --git a/src/test/java/PoliqarpPlusTreeTest.java b/src/test/java/PoliqarpPlusTreeTest.java
index 18f2612..43da3a1 100644
--- a/src/test/java/PoliqarpPlusTreeTest.java
+++ b/src/test/java/PoliqarpPlusTreeTest.java
@@ -150,8 +150,7 @@
 	}
 	
 	@Test
-	public void testElements() throws QueryException {
-		String query;
+	public void testSpans() throws QueryException {
 		// <s>
 		String elem1 = "{@type=korap:span, key=s}";
 		assertTrue(equalsQueryContent(elem1, "<s>"));
@@ -173,6 +172,56 @@
 		ppt = new PoliqarpPlusTree(query);
 		map = ppt.getRequestMap().get("query").toString();
 		assertEquals(span4.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		// span negation
+		query = "<cnx/c!=vp>";
+		expected = "{@type=korap:span, foundry=cnx, layer=c, key=vp, match=match:ne}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		// span negation
+		query = "<cnx/c!=vp>";
+		expected = "{@type=korap:span, foundry=cnx, layer=c, key=vp, match=match:ne}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "<cnx/c=vp class!=header>";
+		expected = "{@type=korap:span, foundry=cnx, layer=c, key=vp, attr={@type=korap:term, key=class, value=header, match=match:ne}}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "<cnx/c=vp !(class=header&id=7)>";
+		expected = 
+			"{@type=korap:span, foundry=cnx, layer=c, key=vp, attr=" +
+				"{@type=korap:termGroup, relation=relation:and, operands=[" +
+					"{@type=korap:term, key=class, value=header, match=match:ne}," +
+					"{@type=korap:term, key=id, value=7, match=match:ne}" +
+				"]}" +
+			"}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "<cnx/c!=vp (class=header&id=7)>";
+		expected = 
+			"{@type=korap:span, foundry=cnx, layer=c, key=vp, match=match:ne, attr=" +
+				"{@type=korap:termGroup, relation=relation:and, operands=[" +
+					"{@type=korap:term, key=class, value=header, match=match:eq}," +
+					"{@type=korap:term, key=id, value=7, match=match:eq}" +
+				"]}" +
+			"}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
+		
+		query = "<cnx/c=vp !!class=header>";
+		expected = "{@type=korap:span, foundry=cnx, layer=c, key=vp, attr={@type=korap:term, key=class, value=header, match=match:eq}}";
+		ppt = new PoliqarpPlusTree(query);
+		map = ppt.getRequestMap().get("query").toString();
+		assertEquals(expected.replaceAll(" ", ""), map.replaceAll(" ", ""));
 	}
 	
 	@Test