Fix foundry and layer handling in attribute groups
Change-Id: I638d96ea03f75349312c95dc0a08bee457f048db
diff --git a/Changes b/Changes
index d78dca0..5a580b1 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,8 @@
0.64.7 2026-04-28
- [bugfix] Keep highlights that extend beyond a cut match
(diewald; fixes #177; diewald; AI-assisted Claude Opus 4.6)
+ - [bugfix] Correctly handle foundry and layer in attribute groups
+ (diewald; AI-assisted Claude Opus 4.6)
0.64.6 2026-03-09
- [performance] Add leaf cache. (diewald)
diff --git a/src/main/java/de/ids_mannheim/korap/KrillQuery.java b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
index 82d3086..c71eb1a 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
@@ -1551,6 +1551,18 @@
String relation = attrNode.get("relation").asText();
JsonNode operands = attrNode.get("operands");
+ // Copy foundry and layer from the attribute group to the operands
+ if (attrNode.has("foundry") || attrNode.has("layer")) {
+ for (JsonNode operand : operands) {
+ if (attrNode.has("foundry") && !operand.has("foundry")) {
+ ((ObjectNode) operand).set("foundry", attrNode.get("foundry"));
+ }
+ if (attrNode.has("layer") && !operand.has("layer")) {
+ ((ObjectNode) operand).set("layer", attrNode.get("layer"));
+ }
+ }
+ }
+
SpanQueryWrapper attrWrapper;
if ("relation:and".equals(relation)) {
List<SpanQueryWrapper> wrapperList = new ArrayList<SpanQueryWrapper>();
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestAttributeIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestAttributeIndex.java
index 06fcf18..2ea1795 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestAttributeIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestAttributeIndex.java
@@ -547,4 +547,51 @@
Result kr = krill.apply(ki);
assertEquals(4, kr.getTotalResults());
}
+
+ @Test
+ public void testAttributeRealIndexAndGroup () throws QueryException, IOException {
+ KrillIndex ki = new KrillIndex();
+ ki.addDoc(getClass().getResourceAsStream("/others/REDEW-DOC1-00001.json.gz"),
+ true);
+ ki.commit();
+
+ String filepath = getClass()
+ .getResource(
+ "/queries/attribute/said-and-attributes.jsonld")
+ .getFile();
+
+ SpanQueryWrapper sqw = getJsonQuery(filepath);
+ Krill krill = new Krill(sqw);
+ assertEquals(
+ "spanElementWithAttribute(<tokens:dereko/s:said />, "
+ + "[spanAttribute(tokens:@:dereko/s:mode:direct), "
+ + "spanAttribute(tokens:@:dereko/s:content:speech)])",
+ krill.getSpanQuery().toString());
+ Result kr = krill.apply(ki);
+ assertEquals(2, kr.getTotalResults());
+ }
+
+ @Test
+ public void testAttributeRealIndexOrGroup () throws QueryException, IOException {
+ KrillIndex ki = new KrillIndex();
+ ki.addDoc(getClass().getResourceAsStream("/others/REDEW-DOC1-00001.json.gz"),
+ true);
+ ki.commit();
+
+ String filepath = getClass()
+ .getResource(
+ "/queries/attribute/said-or-attributes.jsonld")
+ .getFile();
+
+ SpanQueryWrapper sqw = getJsonQuery(filepath);
+ Krill krill = new Krill(sqw);
+ assertEquals(
+ "spanOr([spanElementWithAttribute(<tokens:dereko/s:said />, "
+ + "spanAttribute(tokens:@:dereko/s:mode:direct)), "
+ + "spanElementWithAttribute(<tokens:dereko/s:said />, "
+ + "spanAttribute(tokens:@:dereko/s:mode:indirect))])",
+ krill.getSpanQuery().toString());
+ Result kr = krill.apply(ki);
+ assertEquals(6, kr.getTotalResults());
+ }
}
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 89e110b..877a37f 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanWithAttributeJSON.java
@@ -189,6 +189,37 @@
@Test
+ public void testElementMultipleAndAttributesWithFoundryAndLayer () throws QueryException {
+ String filepath = getClass()
+ .getResource(
+ "/queries/attribute/element-multiple-and-attributes-with-fl.jsonld")
+ .getFile();
+ SpanQueryWrapper sqwi = getJsonQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "spanElementWithAttribute(<tokens:dereko/s:head />, "
+ + "[spanAttribute(tokens:@:dereko/s:type:top), "
+ + "spanAttribute(tokens:@:dereko/s:type:main)])",
+ sq.toString());
+ }
+
+ @Test
+ public void testElementMultipleOrAttributesWithFoundryAndLayer () throws QueryException {
+ String filepath = getClass()
+ .getResource(
+ "/queries/attribute/element-multiple-or-attributes-with-fl.jsonld")
+ .getFile();
+ SpanQueryWrapper sqwi = getJsonQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "spanOr([spanElementWithAttribute(<tokens:dereko/s:head />, "
+ + "spanAttribute(tokens:@:dereko/s:type:top)), "
+ + "spanElementWithAttribute(<tokens:dereko/s:head />, "
+ + "spanAttribute(tokens:@:dereko/s:type:main))])",
+ sq.toString());
+ }
+
+ @Test
public void testAnyElementSingleNotAttribute () throws QueryException {
String filepath = getClass()
diff --git a/src/test/resources/queries/attribute/element-multiple-and-attributes-with-fl.jsonld b/src/test/resources/queries/attribute/element-multiple-and-attributes-with-fl.jsonld
new file mode 100644
index 0000000..83e2f57
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-multiple-and-attributes-with-fl.jsonld
@@ -0,0 +1,37 @@
+{
+ "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+ "errors": [],
+ "warnings": [],
+ "messages": [],
+ "collection": {},
+ "query": {
+ "@type": "koral:span",
+ "attr": {
+ "@type": "koral:termGroup",
+ "foundry": "dereko",
+ "layer": "s",
+ "operands": [
+ {
+ "@type": "koral:term",
+ "key": "type",
+ "match": "match:eq",
+ "value": "top"
+ },
+ {
+ "@type": "koral:term",
+ "key": "type",
+ "match": "match:eq",
+ "value": "main"
+ }
+ ],
+ "relation": "relation:and"
+ },
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "dereko",
+ "key": "head",
+ "layer": "s"
+ }
+ },
+ "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/element-multiple-or-attributes-with-fl.jsonld b/src/test/resources/queries/attribute/element-multiple-or-attributes-with-fl.jsonld
new file mode 100644
index 0000000..1126df2
--- /dev/null
+++ b/src/test/resources/queries/attribute/element-multiple-or-attributes-with-fl.jsonld
@@ -0,0 +1,37 @@
+{
+ "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+ "errors": [],
+ "warnings": [],
+ "messages": [],
+ "collection": {},
+ "query": {
+ "@type": "koral:span",
+ "attr": {
+ "@type": "koral:termGroup",
+ "foundry": "dereko",
+ "layer": "s",
+ "operands": [
+ {
+ "@type": "koral:term",
+ "key": "type",
+ "match": "match:eq",
+ "value": "top"
+ },
+ {
+ "@type": "koral:term",
+ "key": "type",
+ "match": "match:eq",
+ "value": "main"
+ }
+ ],
+ "relation": "relation:or"
+ },
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "dereko",
+ "key": "head",
+ "layer": "s"
+ }
+ },
+ "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/said-and-attributes.jsonld b/src/test/resources/queries/attribute/said-and-attributes.jsonld
new file mode 100644
index 0000000..cb4a74d
--- /dev/null
+++ b/src/test/resources/queries/attribute/said-and-attributes.jsonld
@@ -0,0 +1,37 @@
+{
+ "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+ "errors": [],
+ "warnings": [],
+ "messages": [],
+ "collection": {},
+ "query": {
+ "@type": "koral:span",
+ "attr": {
+ "@type": "koral:termGroup",
+ "foundry": "dereko",
+ "layer": "s",
+ "operands": [
+ {
+ "@type": "koral:term",
+ "key": "mode",
+ "match": "match:eq",
+ "value": "direct"
+ },
+ {
+ "@type": "koral:term",
+ "key": "content",
+ "match": "match:eq",
+ "value": "speech"
+ }
+ ],
+ "relation": "relation:and"
+ },
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "dereko",
+ "key": "said",
+ "layer": "s"
+ }
+ },
+ "meta": {}
+}
diff --git a/src/test/resources/queries/attribute/said-or-attributes.jsonld b/src/test/resources/queries/attribute/said-or-attributes.jsonld
new file mode 100644
index 0000000..3b027f0
--- /dev/null
+++ b/src/test/resources/queries/attribute/said-or-attributes.jsonld
@@ -0,0 +1,37 @@
+{
+ "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.2/context.jsonld",
+ "errors": [],
+ "warnings": [],
+ "messages": [],
+ "collection": {},
+ "query": {
+ "@type": "koral:span",
+ "attr": {
+ "@type": "koral:termGroup",
+ "foundry": "dereko",
+ "layer": "s",
+ "operands": [
+ {
+ "@type": "koral:term",
+ "key": "mode",
+ "match": "match:eq",
+ "value": "direct"
+ },
+ {
+ "@type": "koral:term",
+ "key": "mode",
+ "match": "match:eq",
+ "value": "indirect"
+ }
+ ],
+ "relation": "relation:or"
+ },
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "dereko",
+ "key": "said",
+ "layer": "s"
+ }
+ },
+ "meta": {}
+}