This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opennlp.git


The following commit(s) were added to refs/heads/main by this push:
     new 84a1d6268 GH-1460: Match escaped tags in the bundled Spanish head 
rules (#1461)
84a1d6268 is described below

commit 84a1d626800528c768558f0b724c21150541ffdf
Author: Richard Zowalla <[email protected]>
AuthorDate: Fri Oct 9 19:51:01 2026 +0200

    GH-1460: Match escaped tags in the bundled Spanish head rules (#1461)
    
    * GH-1460: Match escaped tags in the bundled Spanish head rules
    
    The Spanish head rules file wrote tags such as GRUP\\.VERB with a double
    backslash, so they required a literal backslash and never matched types
    such as GRUP.VERB, GRUP.NOM, GRUP.A, GRUP.ADV or S.A. Use single
    backslashes in both copies and test the bundled rules against the
    intended constituent types.
    
    * GH-1460: Load the Spanish head rules test copy from the test classpath
    
    The test reads a copy of es-head-rules from the runtime test resources 
instead of
    locating opennlp-tools/lang through the working directory.
---
 .../lang/es/AncoraSpanishHeadRulesBundledTest.java | 147 +++++++++++++++++++++
 .../resources/opennlp/tools/parser/es_head_rules   |  22 +++
 opennlp-eval-tests/lang/es/parser/es-head-rules    |  24 ++--
 opennlp-tools/lang/es/parser/es-head-rules         |  24 ++--
 rat-excludes                                       |   1 +
 5 files changed, 194 insertions(+), 24 deletions(-)

diff --git 
a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesBundledTest.java
 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesBundledTest.java
new file mode 100644
index 000000000..8c358aedf
--- /dev/null
+++ 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesBundledTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package opennlp.tools.parser.lang.es;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import opennlp.tools.parser.Parse;
+import opennlp.tools.util.Span;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Tests a copy of the head rules shipped in {@code 
opennlp-tools/lang/es/parser/es-head-rules}
+ * with {@link AncoraSpanishHeadRules}.
+ */
+public class AncoraSpanishHeadRulesBundledTest {
+
+  private static final String BUNDLED_RULES = 
"/opennlp/tools/parser/es_head_rules";
+
+  private static final String FILLER = "FILLER";
+
+  private static AncoraSpanishHeadRules headRules;
+
+  @BeforeAll
+  static void loadBundledRules() throws IOException {
+    try (InputStream rules = AncoraSpanishHeadRulesBundledTest.class
+        .getResourceAsStream(BUNDLED_RULES)) {
+      assertNotNull(rules, "Bundled rules not found: " + BUNDLED_RULES);
+      try (Reader in = new InputStreamReader(rules, StandardCharsets.UTF_8)) {
+        headRules = new AncoraSpanishHeadRules(in);
+      }
+    }
+  }
+
+  private static Parse leaf(String type, int index) {
+    return new Parse("w" + index, new Span(index * 2, index * 2 + 1), type, 
1.0, index);
+  }
+
+  /**
+   * Builds the children so that the rule's fallback (first child for 
left-to-right rules,
+   * last child otherwise) is the filler, which is therefore only selected 
when no rule
+   * tag matches {@code childType}.
+   */
+  private static Parse[] children(Parse candidate, Parse filler, boolean 
leftToRight) {
+    return leftToRight ? new Parse[] {filler, candidate} : new Parse[] 
{candidate, filler};
+  }
+
+  /**
+   * Each escaped tag of the bundled rules must match its literal constituent 
type, so that
+   * the constituent is chosen as head instead of the rule's fallback.
+   *
+   * @param ruleType The constituent type whose head rule is applied.
+   * @param leftToRight Whether the rule searches left to right.
+   * @param childType A constituent type that one of the rule's escaped tags 
must match.
+   */
+  @ParameterizedTest(name = "{0}: {2}")
+  @CsvSource({
+      "S, false, GRUP.VERB",
+      "S, false, GRUP.NOM",
+      "SENTENCE, false, GRUP.VERB",
+      "SENTENCE, false, GRUP.NOM",
+      "SA, false, $",
+      "SA, false, GRUP.ADV",
+      "SA, false, S.A",
+      "SA, false, GRUP.A",
+      "SA, false, GRUP.NOM",
+      "S.A, false, S.A",
+      "GRUP.A, true, GRUP.A",
+      "GRUP.ADV, false, GRUP.ADV",
+      "SADV, true, GRUP.ADV",
+      "SADV, true, S.A",
+      "GRUP.VERB, false, GRUP.VERB",
+      "MORFEMA.PRONOMINAL, false, GRUP.NOM",
+      "MORFEMA.PRONOMINAL, false, GRUP.VERB",
+      "MORFEMA.VERBAL, false, GRUP.VERB",
+      "INC, false, S.A",
+      "RELATIU, false, GRUP.VERB"
+  })
+  void testEscapedTagMatchesLiteralType(String ruleType, boolean leftToRight, 
String childType) {
+    Parse candidate = leaf(childType, 0);
+    Parse filler = leaf(FILLER, 1);
+
+    assertSame(candidate, headRules.getHead(children(candidate, filler, 
leftToRight), ruleType));
+  }
+
+  /**
+   * An escaped dot must match only a literal dot, so types that differ from 
the intended
+   * type in that position must not be chosen as head.
+   *
+   * @param ruleType The constituent type whose head rule is applied.
+   * @param leftToRight Whether the rule searches left to right.
+   * @param childType A constituent type that no tag of the rule may match.
+   */
+  @ParameterizedTest(name = "{0}: {2}")
+  @CsvSource({
+      "S, false, GRUPXVERB",
+      "S, false, GRUPXNOM",
+      "SA, false, GRUPXADV",
+      "SA, false, SXA",
+      "SA, false, GRUPXA",
+      "GRUP.VERB, false, GRUPXVERB",
+      "MORFEMA.VERBAL, false, GRUPXVERB"
+  })
+  void testEscapedTagRejectsNonLiteralType(String ruleType, boolean 
leftToRight, String childType) {
+    Parse candidate = leaf(childType, 0);
+    Parse filler = leaf(FILLER, 1);
+
+    assertSame(filler, headRules.getHead(children(candidate, filler, 
leftToRight), ruleType));
+  }
+
+  /**
+   * In a GRUP.VERB constituent, a nested GRUP.VERB precedes SA in the rule's 
tag order and
+   * must therefore be chosen as head over an SA sibling.
+   */
+  @Test
+  void testGrupVerbHeadPrefersNestedGrupVerbOverSa() {
+    Parse verb = leaf("GRUP.VERB", 0);
+    Parse adjective = leaf("SA", 1);
+
+    assertSame(verb, headRules.getHead(new Parse[] {verb, adjective}, 
"GRUP.VERB"));
+  }
+}
diff --git 
a/opennlp-core/opennlp-runtime/src/test/resources/opennlp/tools/parser/es_head_rules
 
b/opennlp-core/opennlp-runtime/src/test/resources/opennlp/tools/parser/es_head_rules
new file mode 100644
index 000000000..d36c9d388
--- /dev/null
+++ 
b/opennlp-core/opennlp-runtime/src/test/resources/opennlp/tools/parser/es_head_rules
@@ -0,0 +1,22 @@
+13 SENTENCE 0 PREP SP[CS].* CS.* GRUP\.VERB S SA COORD CONJ GRUP\.NOM SN S
+12 S 0 PREP SP[CS].* COORD CONJ CS.* GRUP\.VERB S SA GRUP\.NOM SN
+22 SA 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* SA S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+21 S.A 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+20 SADV 1 S RG RN SADV GRUP\.ADV SP[CS].* PREP Z.* AQA.* AQC.* S\.A GRUP\.A 
CONJ CS.* SN GRUP\.NOM AQS.* NC.*S.*
+8 SP 0 SP[CS].* PREP CS.* CONJ V[MAS]G.* V[MAS]P.*
+20 GRUP.A 1 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+18 GRUP.ADV 0 RG RN GRUP\.ADV PREP SP.* Z.* AQA.* AQC.* GRUP\.A S\.A CS.* CONJ 
SN GRUP\.NOM AQS.* NC.*S.*
+23 GRUP.VERB 0 INFINITIU GERUNDI PARTICIPI PREP SP[CS].* V[MAS].*[IS].* 
V[MAS]P.* V.*C.* V[MAS]IP3S.* V.* V[MAS]G.* V[MAS]IP[12]S.* GRUP\.VERB SA S\.A 
GRUP\.A NC.*S.* NC.*P.* GRUP\.NOM SN S
+5 INFINITIU 0 VMN.* V[MAS]N.* V.*
+5 GERUNDI 0 VMG.* V[MAS]G.* V.*
+5 PARTICIPI 0 VMP.* V[MAS]P.* V.*
+6 MORFEMA.PRONOMINAL 0 P.* SN.* GRUP\.NOM.* GRUP\.VERB
+7 MORFEMA.VERBAL 0 GRUP\.VERB P.* SN.* GRUP\.NOM.* S
+9 COORD 1 CONJ CC.* RB RN SP[CS].* PREP CS
+16 INC 0 S SN GRUP\.NOM GRUP\.VERB SADV GRUP.ADV SA S\.A GRUP\.A PREP SP[CS].* 
CONJ CS D.*
+3 INTERJECCIO 0 I
+3 NEG 0 RN
+6 PREP 0 PREP SP[CS].* CONJ CS
+7 RELATIU 0 P.* SN GRUP\.NOM S GRUP\.VERB
+2 SPEC 0
+2 X 1
diff --git a/opennlp-eval-tests/lang/es/parser/es-head-rules 
b/opennlp-eval-tests/lang/es/parser/es-head-rules
index 1db3f5840..d36c9d388 100644
--- a/opennlp-eval-tests/lang/es/parser/es-head-rules
+++ b/opennlp-eval-tests/lang/es/parser/es-head-rules
@@ -1,22 +1,22 @@
-13 SENTENCE 0 PREP SP[CS].* CS.* GRUP\\.VERB S SA COORD CONJ GRUP\\.NOM SN S
-12 S 0 PREP SP[CS].* COORD CONJ CS.* GRUP\\.VERB S SA GRUP\\.NOM SN
-22 SA 0 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* SA S\\.A GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-21 S.A 0 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* S\\.A GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-20 SADV 1 S RG RN SADV GRUP\\.ADV SP[CS].* PREP Z.* AQA.* AQC.* S\\.A GRUP\\.A 
CONJ CS.* SN GRUP\\.NOM AQS.* NC.*S.*
+13 SENTENCE 0 PREP SP[CS].* CS.* GRUP\.VERB S SA COORD CONJ GRUP\.NOM SN S
+12 S 0 PREP SP[CS].* COORD CONJ CS.* GRUP\.VERB S SA GRUP\.NOM SN
+22 SA 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* SA S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+21 S.A 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+20 SADV 1 S RG RN SADV GRUP\.ADV SP[CS].* PREP Z.* AQA.* AQC.* S\.A GRUP\.A 
CONJ CS.* SN GRUP\.NOM AQS.* NC.*S.*
 8 SP 0 SP[CS].* PREP CS.* CONJ V[MAS]G.* V[MAS]P.*
-20 GRUP.A 1 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* 
V[MAS]P.* V[MAS]G.* GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-18 GRUP.ADV 0 RG RN GRUP\\.ADV PREP SP.* Z.* AQA.* AQC.* GRUP\\.A S\\.A CS.* 
CONJ SN GRUP\\.NOM AQS.* NC.*S.*
-23 GRUP.VERB 0 INFINITIU GERUNDI PARTICIPI PREP SP[CS].* V[MAS].*[IS].* 
V[MAS]P.* V.*C.* V[MAS]IP3S.* V.* V[MAS]G.* V[MAS]IP[12]S.* GRUP\\.VERB SA 
S\\.A GRUP\\.A NC.*S.* NC.*P.* GRUP\\.NOM SN S
+20 GRUP.A 1 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+18 GRUP.ADV 0 RG RN GRUP\.ADV PREP SP.* Z.* AQA.* AQC.* GRUP\.A S\.A CS.* CONJ 
SN GRUP\.NOM AQS.* NC.*S.*
+23 GRUP.VERB 0 INFINITIU GERUNDI PARTICIPI PREP SP[CS].* V[MAS].*[IS].* 
V[MAS]P.* V.*C.* V[MAS]IP3S.* V.* V[MAS]G.* V[MAS]IP[12]S.* GRUP\.VERB SA S\.A 
GRUP\.A NC.*S.* NC.*P.* GRUP\.NOM SN S
 5 INFINITIU 0 VMN.* V[MAS]N.* V.*
 5 GERUNDI 0 VMG.* V[MAS]G.* V.*
 5 PARTICIPI 0 VMP.* V[MAS]P.* V.*
-6 MORFEMA.PRONOMINAL 0 P.* SN.* GRUP\\.NOM.* GRUP\\.VERB
-7 MORFEMA.VERBAL 0 GRUP\\.VERB P.* SN.* GRUP\\.NOM.* S
+6 MORFEMA.PRONOMINAL 0 P.* SN.* GRUP\.NOM.* GRUP\.VERB
+7 MORFEMA.VERBAL 0 GRUP\.VERB P.* SN.* GRUP\.NOM.* S
 9 COORD 1 CONJ CC.* RB RN SP[CS].* PREP CS
-16 INC 0 S SN GRUP\\.NOM GRUP\\.VERB SADV GRUP.ADV SA S\\.A GRUP\\.A PREP 
SP[CS].* CONJ CS D.*
+16 INC 0 S SN GRUP\.NOM GRUP\.VERB SADV GRUP.ADV SA S\.A GRUP\.A PREP SP[CS].* 
CONJ CS D.*
 3 INTERJECCIO 0 I
 3 NEG 0 RN
 6 PREP 0 PREP SP[CS].* CONJ CS
-7 RELATIU 0 P.* SN GRUP\\.NOM S GRUP\\.VERB
+7 RELATIU 0 P.* SN GRUP\.NOM S GRUP\.VERB
 2 SPEC 0
 2 X 1
diff --git a/opennlp-tools/lang/es/parser/es-head-rules 
b/opennlp-tools/lang/es/parser/es-head-rules
index 1db3f5840..d36c9d388 100644
--- a/opennlp-tools/lang/es/parser/es-head-rules
+++ b/opennlp-tools/lang/es/parser/es-head-rules
@@ -1,22 +1,22 @@
-13 SENTENCE 0 PREP SP[CS].* CS.* GRUP\\.VERB S SA COORD CONJ GRUP\\.NOM SN S
-12 S 0 PREP SP[CS].* COORD CONJ CS.* GRUP\\.VERB S SA GRUP\\.NOM SN
-22 SA 0 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* SA S\\.A GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-21 S.A 0 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* S\\.A GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-20 SADV 1 S RG RN SADV GRUP\\.ADV SP[CS].* PREP Z.* AQA.* AQC.* S\\.A GRUP\\.A 
CONJ CS.* SN GRUP\\.NOM AQS.* NC.*S.*
+13 SENTENCE 0 PREP SP[CS].* CS.* GRUP\.VERB S SA COORD CONJ GRUP\.NOM SN S
+12 S 0 PREP SP[CS].* COORD CONJ CS.* GRUP\.VERB S SA GRUP\.NOM SN
+22 SA 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* SA S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+21 S.A 0 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* S\.A GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+20 SADV 1 S RG RN SADV GRUP\.ADV SP[CS].* PREP Z.* AQA.* AQC.* S\.A GRUP\.A 
CONJ CS.* SN GRUP\.NOM AQS.* NC.*S.*
 8 SP 0 SP[CS].* PREP CS.* CONJ V[MAS]G.* V[MAS]P.*
-20 GRUP.A 1 NC.*P.* GRUP\\.NOM \\$ NC.*S.* SADV GRUP\\.ADV AQA.* AQC.* 
V[MAS]P.* V[MAS]G.* GRUP\\.A AQS.* SN GRUP\\.NOM D.* S RG RN
-18 GRUP.ADV 0 RG RN GRUP\\.ADV PREP SP.* Z.* AQA.* AQC.* GRUP\\.A S\\.A CS.* 
CONJ SN GRUP\\.NOM AQS.* NC.*S.*
-23 GRUP.VERB 0 INFINITIU GERUNDI PARTICIPI PREP SP[CS].* V[MAS].*[IS].* 
V[MAS]P.* V.*C.* V[MAS]IP3S.* V.* V[MAS]G.* V[MAS]IP[12]S.* GRUP\\.VERB SA 
S\\.A GRUP\\.A NC.*S.* NC.*P.* GRUP\\.NOM SN S
+20 GRUP.A 1 NC.*P.* GRUP\.NOM \$ NC.*S.* SADV GRUP\.ADV AQA.* AQC.* V[MAS]P.* 
V[MAS]G.* GRUP\.A AQS.* SN GRUP\.NOM D.* S RG RN
+18 GRUP.ADV 0 RG RN GRUP\.ADV PREP SP.* Z.* AQA.* AQC.* GRUP\.A S\.A CS.* CONJ 
SN GRUP\.NOM AQS.* NC.*S.*
+23 GRUP.VERB 0 INFINITIU GERUNDI PARTICIPI PREP SP[CS].* V[MAS].*[IS].* 
V[MAS]P.* V.*C.* V[MAS]IP3S.* V.* V[MAS]G.* V[MAS]IP[12]S.* GRUP\.VERB SA S\.A 
GRUP\.A NC.*S.* NC.*P.* GRUP\.NOM SN S
 5 INFINITIU 0 VMN.* V[MAS]N.* V.*
 5 GERUNDI 0 VMG.* V[MAS]G.* V.*
 5 PARTICIPI 0 VMP.* V[MAS]P.* V.*
-6 MORFEMA.PRONOMINAL 0 P.* SN.* GRUP\\.NOM.* GRUP\\.VERB
-7 MORFEMA.VERBAL 0 GRUP\\.VERB P.* SN.* GRUP\\.NOM.* S
+6 MORFEMA.PRONOMINAL 0 P.* SN.* GRUP\.NOM.* GRUP\.VERB
+7 MORFEMA.VERBAL 0 GRUP\.VERB P.* SN.* GRUP\.NOM.* S
 9 COORD 1 CONJ CC.* RB RN SP[CS].* PREP CS
-16 INC 0 S SN GRUP\\.NOM GRUP\\.VERB SADV GRUP.ADV SA S\\.A GRUP\\.A PREP 
SP[CS].* CONJ CS D.*
+16 INC 0 S SN GRUP\.NOM GRUP\.VERB SADV GRUP.ADV SA S\.A GRUP\.A PREP SP[CS].* 
CONJ CS D.*
 3 INTERJECCIO 0 I
 3 NEG 0 RN
 6 PREP 0 PREP SP[CS].* CONJ CS
-7 RELATIU 0 P.* SN GRUP\\.NOM S GRUP\\.VERB
+7 RELATIU 0 P.* SN GRUP\.NOM S GRUP\.VERB
 2 SPEC 0
 2 X 1
diff --git a/rat-excludes b/rat-excludes
index 53c444cf6..09d8e7f5d 100644
--- a/rat-excludes
+++ b/rat-excludes
@@ -22,6 +22,7 @@ src/test/resources/opennlp/tools/*/*.txt
 src/test/resources/opennlp/tools/*/*.train
 src/test/resources/opennlp/tools/parser/en_head_rules
 src/test/resources/opennlp/tools/parser/en_head_rules
+src/test/resources/opennlp/tools/parser/es_head_rules
 src/test/resources/opennlp/tools/parser/parser.train
 src/test/resources/opennlp/tools/parser/test.parse
 src/test/resources/opennlp/tools/postag/AnnotatedSentences.txt

Reply via email to