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