Fix deserialization of spans with attributes

Change-Id: I6ce4f0d1a16ff50b8e79eb2bafe96560ff707b6b
diff --git a/Changes b/Changes
index 59f093c..cdf8c49 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,6 @@
-0.59.2 2020-06-02
+0.59.2 2020-06-17
     - [feature] Add fingerprint method to index (diewald)
+    - [bugfix] Fix deserialization of spans with attributes (diewald)
 
 0.59.1 2020-04-08
     - [bugfix] Fix bug in classed group queries (diewald)
diff --git a/src/main/java/de/ids_mannheim/korap/KrillQuery.java b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
index abfc16e..8853064 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
@@ -314,6 +314,15 @@
                 if (!json.has("wrap"))
                     return new SpanRepetitionQueryWrapper();
 
+                // Workaround so that "attr" can be wrapped (legacy) and be
+                // next to wrap as well (spec)
+
+                // Term has attribute
+                if (json.has("attr")) {
+                    JsonNode attrNode = json.get("attr");
+                    ((ObjectNode)json.get("wrap")).put("attr",attrNode);
+                };
+                
                 // Get wrapped token
                 return this._segFromJson(json.get("wrap"));
 
@@ -326,6 +335,15 @@
                 if (!json.has("wrap"))
                     return this._termFromJson(json);
 
+                // Workaround so that "attr" can be wrapped (legacy) and be
+                // next to wrap as well (spec)
+
+                // Term has attribute
+                if (json.has("attr")) {
+                    JsonNode attrNode = json.get("attr");
+                    ((ObjectNode)json.get("wrap")).put("attr",attrNode);
+                };
+                
                 // This is an ugly hack
                 return this._termFromJson(json.get("wrap"), true);
         };
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java
index 5353de1..23a1beb 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java
@@ -29,6 +29,18 @@
                 sq.toString());
     }
 
+    @Test
+    public void testElementSingleAttributeBug () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/attribute/element-single-attribute-2.jsonld")
+                .getFile();
+        SpanQueryWrapper sqwi = getJsonQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals(
+                "spanElementWithAttribute(<tokens:dereko/s:said />, spanAttribute(tokens:mode:indirects))",
+                sq.toString());
+    }
 
     @Test
     public void testElementSingleNotAttribute () throws QueryException {
@@ -43,6 +55,19 @@
                 sq.toString());
     }
 
+    @Test
+    public void testElementSingleNotAttribute2 () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/attribute/element-single-not-attribute-2.jsonld")
+                .getFile();
+        SpanQueryWrapper sqwi = getJsonQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals(
+                "spanElementWithAttribute(<tokens:head />, spanAttribute(!tokens:type:top))",
+                sq.toString());
+    }
+    
 
     @Test
     public void testElementMultipleAndNotAttributes () throws QueryException {
@@ -58,6 +83,20 @@
                 sq.toString());
     }
 
+    @Test
+    public void testElementMultipleAndNotAttributes2 () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/attribute/element-multiple-and-not-attributes-2.jsonld")
+                .getFile();
+        SpanQueryWrapper sqwi = getJsonQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals(
+                "spanElementWithAttribute(<tokens:div />, [spanAttribute(tokens:type:Zeitschrift), "
+                        + "spanAttribute(!tokens:complete:Y), spanAttribute(tokens:n:0)])",
+                sq.toString());
+    }
+    
 
     @Test
     public void testElementMultipleOrAttributes () throws QueryException {
@@ -76,6 +115,22 @@
 
 
     @Test
+    public void testElementMultipleOrAttributes2 () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/attribute/element-multiple-or-attributes-2.jsonld")
+                .getFile();
+        SpanQueryWrapper sqwi = getJsonQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals(
+                "spanOr([spanElementWithAttribute(<tokens:div />, spanAttribute(tokens:type:Zeitschrift)), "
+                        + "spanElementWithAttribute(<tokens:div />, spanAttribute(tokens:complete:Y)), "
+                        + "spanElementWithAttribute(<tokens:div />, spanAttribute(tokens:n:0))])",
+                sq.toString());
+    }
+    
+
+    @Test
     public void testAnyElementWithAttribute () throws QueryException {
         String filepath = getClass()
                 .getResource(
@@ -87,7 +142,6 @@
                 sq.toString());
     }
 
-
     @Test
     public void testAnyElementWithMultipleOrAttributes ()
             throws QueryException {
diff --git a/src/test/resources/queries/attribute/element-multiple-and-not-attributes-2.jsonld b/src/test/resources/queries/attribute/element-multiple-and-not-attributes-2.jsonld
new file mode 100644
index 0000000..03d5dcd
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-multiple-and-not-attributes-2.jsonld
@@ -0,0 +1,39 @@
+{
+  "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+  "errors": [],
+  "warnings": [],
+  "messages": [],
+  "collection": {},
+  "query": {
+    "@type": "koral:span",
+    "wrap" : {
+      "@type": "koral:term",
+      "key": "div"
+    },
+    "attr": {
+      "@type": "koral:termGroup",
+      "relation": "relation:and",
+      "operands": [
+        {
+          "@type": "koral:term",
+          "layer": "type",
+          "key": "Zeitschrift",
+          "match": "match:eq"
+        },
+        {
+          "@type": "koral:term",
+          "layer": "complete",
+          "key": "Y",
+          "match": "match:ne"
+        },
+        {
+          "@type": "koral:term",
+          "layer": "n",
+          "key": "0",
+          "match": "match:eq"
+        }
+      ]
+    }
+  },
+  "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/element-multiple-or-attributes-2.jsonld b/src/test/resources/queries/attribute/element-multiple-or-attributes-2.jsonld
new file mode 100644
index 0000000..2673364
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-multiple-or-attributes-2.jsonld
@@ -0,0 +1,39 @@
+{
+  "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+  "errors": [],
+  "warnings": [],
+  "messages": [],
+  "collection": {},
+  "query": {
+    "@type": "koral:span",
+    "wrap" : {
+      "@type": "koral:term",
+      "key": "div"
+    },
+    "attr": {
+      "@type": "koral:termGroup",
+      "relation": "relation:or",
+      "operands": [
+        {
+          "@type": "koral:term",
+          "layer": "type",
+          "key": "Zeitschrift",
+          "match": "match:eq"
+        },
+        {
+          "@type": "koral:term",
+          "layer": "complete",
+          "key": "Y",
+          "match": "match:eq"
+        },
+        {
+          "@type": "koral:term",
+          "layer": "n",
+          "key": "0",
+          "match": "match:eq"
+        }
+      ]
+    }
+  },
+  "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/element-single-attribute-2.jsonld b/src/test/resources/queries/attribute/element-single-attribute-2.jsonld
new file mode 100644
index 0000000..59d0c74
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-single-attribute-2.jsonld
@@ -0,0 +1,23 @@
+{
+  "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+  "errors": [],
+  "warnings": [],
+  "messages": [],
+  "collection": {},
+  "query": {
+    "@type": "koral:span",
+    "attr": {
+      "@type": "koral:term",
+      "key": "mode",
+      "match": "match:eq",
+      "value": "indirects"
+    },
+    "wrap": {
+      "@type": "koral:term",
+      "foundry": "dereko",
+      "key": "said",
+      "layer": "s"
+    }
+  },
+  "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/element-single-not-attribute-2.jsonld b/src/test/resources/queries/attribute/element-single-not-attribute-2.jsonld
new file mode 100644
index 0000000..b7b4a09
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-single-not-attribute-2.jsonld
@@ -0,0 +1,21 @@
+{
+  "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+  "errors": [],
+  "warnings": [],
+  "messages": [],
+  "collection": {},
+  "query": {
+    "@type": "koral:span",
+    "wrap" : {
+      "@type": "koral:term",
+      "key": "head"
+    },
+    "attr": {
+      "@type": "koral:term",
+      "layer": "type",
+      "match": "match:ne",
+      "key": "top"
+    }
+  },
+  "meta": {}
+}