Improve handling of C2 #in frames serialization

Change-Id: I8695d8378a17c0e66970fdd00bb8f189b883385c
diff --git a/Changes b/Changes
index 8879cc9..7d63f83 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,8 @@
 0.62.3 2024-04-16
     - [cleanup] Added getDocBitsSupplier to VirtualCorpusFilter (margaretha)
     - [feature] Make VC cache location customizable (margaretha)
+    - [bugfix] Improve handling of C2 #IN frames serialization
+      (diewald)
 
 0.62.2 2024-02-20
     - [feature] Support MMap directory parameters directly
diff --git a/src/main/java/de/ids_mannheim/korap/KrillQuery.java b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
index 2746c35..78aab5a 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
@@ -668,9 +668,46 @@
         if (json.has("frames")) {
             JsonNode frameN = json.get("frames");
             if (frameN.isArray()) {
-                frameN = json.get("frames").get(0);
-                if (frameN != null && frameN.isValueNode())
-                    frame = frameN.asText().substring(7);
+
+                // Treat the following temporarilly as "contains"
+                // "frames:matches",
+                // "frames:startsWith",
+                // "frames:endsWith",
+                // "frames:isAround"
+                // This ignores nonunique frames
+
+                int fs = 0;
+                for (JsonNode frameS : frameN) {
+                    switch (frameS.asText()) {
+                    case "frames:matches":
+                        fs++;
+                        break;
+                    case "frames:startsWith":
+                        fs++;
+                        break;
+                    case "frames:isAround":
+                        fs++;
+                        break;
+                    case "frames:endsWith":
+                        fs++;
+                        break;
+                    default:
+                        fs+=7;
+                    }
+                };
+
+                
+                if (fs == 4) {
+                    frame = "contains";
+                } else {
+                    if (frameN.size() > 1) {
+                        this.addMessage(0, "Frames not fully supported yet");
+                    };
+                    
+                    frameN = frameN.get(0);
+                    if (frameN != null && frameN.isValueNode())
+                        frame = frameN.asText().substring(7);
+                };
             };
         }
         // <legacyCode>
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanWithinQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanWithinQueryJSON.java
index 42584b3..350b59c 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanWithinQueryJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanWithinQueryJSON.java
@@ -27,4 +27,17 @@
         SpanQueryWrapper sqwi = getJsonQuery(filepath);
         SpanQuery sq = sqwi.toQuery();
     }
+
+    @Test
+    public void testSequenceContainSentence () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/position/sequence-contain-sentence.json")
+                .getFile();
+        SpanQueryWrapper sqwi = getJsonQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+
+        assertEquals("focus(129: spanContain(<tokens:base/s />, {129: spanNext(tokens:marmot/p:ADJA, tokens:i:baum)}),sorting)", sq.toString());
+    }
+
 }
diff --git a/src/test/resources/queries/position/sequence-contain-sentence.json b/src/test/resources/queries/position/sequence-contain-sentence.json
new file mode 100644
index 0000000..5e3075a
--- /dev/null
+++ b/src/test/resources/queries/position/sequence-contain-sentence.json
@@ -0,0 +1,85 @@
+{
+  "@context": "http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld",
+  "query": {
+    "@type": "koral:reference",
+    "classRef": [
+      129
+    ],
+    "operands": [
+      {
+        "@type": "koral:group",
+        "frames": [
+          "frames:matches",
+          "frames:startsWith",
+          "frames:endsWith",
+          "frames:isAround"
+        ],
+        "operands": [
+          {
+            "@type": "koral:span",
+            "rewrites": [
+              {
+                "@type": "koral:rewrite",
+                "operation": "operation:injection",
+                "scope": "foundry",
+                "src": "Kustvakt"
+              }
+            ],
+            "wrap": {
+              "@type": "koral:term",
+              "foundry": "base",
+              "key": "s",
+              "match": "match:eq"
+            }
+          },
+          {
+            "@type": "koral:group",
+            "classOut": 129,
+            "operands": [
+              {
+                "@type": "koral:group",
+                "operands": [
+                  {
+                    "@type": "koral:token",
+                    "wrap": {
+                      "@type": "koral:term",
+                      "foundry": "marmot",
+                      "key": "ADJA",
+                      "layer": "p",
+                      "match": "match:eq"
+                    }
+                  },
+                  {
+                    "@type": "koral:token",
+                    "wrap": {
+                      "@type": "koral:term",
+                      "flags": [
+                        "flags:caseInsensitive"
+                      ],
+                      "foundry": "opennlp",
+                      "key": "Baum",
+                      "layer": "orth",
+                      "match": "match:eq",
+                      "rewrites": [
+                        {
+                          "@type": "koral:rewrite",
+                          "operation": "operation:injection",
+                          "scope": "foundry",
+                          "src": "Kustvakt"
+                        }
+                      ]
+                    }
+                  }
+                ],
+                "operation": "operation:sequence"
+              }
+            ],
+            "operation": "operation:class"
+          }
+        ],
+        "operation": "operation:position"
+      }
+    ],
+    "operation": "operation:focus"
+  }
+}