[ 
https://issues.apache.org/jira/browse/OPENNLP-1990?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18120857#comment-18120857
 ] 

ASF GitHub Bot commented on OPENNLP-1990:
-----------------------------------------

rzo1 commented on code in PR #1322:
URL: https://github.com/apache/opennlp/pull/1322#discussion_r4136879559


##########
opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascIdentifiersTest.java:
##########
@@ -0,0 +1,128 @@
+/*
+ * 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.formats.masc;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.tools.util.Span;
+
+public class MascIdentifiersTest {
+
+  @ParameterizedTest
+  @CsvSource({"ne-n7, ne-n, 7", "penn-n12, penn-n, 12", "seg-r0, seg-r, 0", 
"seg-r00, seg-r, 0",
+      "penn-n007, penn-n, 7", "ne-n2147483647, ne-n, 2147483647"})
+  void testParseIdReadsTheNumberAfterThePrefix(String id, String prefix, int 
expected) {
+    Assertions.assertEquals(expected, MascIdentifiers.parseId(id, prefix));
+  }
+
+  @ParameterizedTest
+  // other or missing prefix, prefix later in the text, doubled prefix, no 
digits, sign,
+  // digits of another script, trailing text, whitespace, and empty
+  @ValueSource(strings = {"7", "xne-n7", "NE-N7", "ne\u2011n7", "ne-nne-n7", 
"ne-n", "ne-n-7",
+      "ne-n+7", "ne-n\u0661", "ne-n\uFF17", "ne-n7\u0661", "ne-n\u06F7", 
"ne-n\u00B2", "ne-n\u2167",
+      "ne-n\uD835\uDFCE", "ne-n7x", "ne-n7 ", " ne-n7", "ne-n7\n", ""})
+  void testParseIdRejectsAnythingElse(String id) {
+    Assertions.assertThrows(IllegalArgumentException.class,
+        () -> MascIdentifiers.parseId(id, 
MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
+  }
+
+  @Test
+  void testParseIdRejectsNull() {
+    Assertions.assertThrows(IllegalArgumentException.class,
+        () -> MascIdentifiers.parseId(null, 
MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
+  }

Review Comment:
   `""` is in the `@ValueSource` and null has its own test. Use 
`@NullAndEmptySource`.



##########
opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascIdentifiers.java:
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.formats.masc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import opennlp.tools.util.Span;
+import opennlp.tools.util.StringUtil;
+
+/**
+ * Parses MASC identifier and anchor attributes; lists are separated by
+ * <a href="https://www.w3.org/TR/xml/#NT-S";>XML whitespace</a>.
+ */
+final class MascIdentifiers {
+
+  /** The prefix of a named entity node identifier, as in {@code ne-n7}. */
+  static final String NAMED_ENTITY_ID_PREFIX = "ne-n";
+
+  /** The prefix of a Penn token node identifier, as in {@code penn-n7}. */
+  static final String PENN_TOKEN_ID_PREFIX = "penn-n";
+
+  /** The prefix of a segmentation region identifier, as in {@code seg-r7}. */
+  static final String REGION_ID_PREFIX = "seg-r";
+
+  private MascIdentifiers() {
+  }
+
+  /**
+   * Parses the number of an identifier that starts with {@code prefix} and 
continues with
+   * one or more ASCII digits only.
+   *
+   * @param id The identifier, such as {@code penn-n7}.
+   * @param prefix The expected prefix, such as {@link #PENN_TOKEN_ID_PREFIX}.
+   * @return The number after the prefix.
+   * @throws IllegalArgumentException Thrown if {@code id} is {@code null}, 
does not start with
+   *         {@code prefix}, is not followed by digits only, or the number 
does not fit
+   *         an {@code int}.
+   */
+  static int parseId(String id, String prefix) {
+    if (id == null || !id.startsWith(prefix)) {
+      throw invalidId(id, prefix, null);
+    }
+    try {
+      return parseAsciiInt(id, prefix.length());
+    } catch (NumberFormatException e) {
+      // reached only when the digits overflow an int
+      throw new IllegalArgumentException("MASC identifier number does not fit 
an int: " + id, e);
+    } catch (IllegalArgumentException e) {
+      throw invalidId(id, prefix, e);
+    }
+  }
+
+  /**
+   * Describes an identifier that is not {@code prefix} followed by ASCII 
digits.
+   *
+   * @param id The rejected identifier.
+   * @param prefix The expected prefix.
+   * @param cause The underlying error, or {@code null}.
+   * @return The exception to throw.
+   */
+  private static IllegalArgumentException invalidId(String id, String prefix, 
Throwable cause) {
+    return new IllegalArgumentException(
+        "MASC identifier must be " + prefix + " followed by digits: " + id, 
cause);
+  }
+
+  /**
+   * Parses an XML whitespace separated list of identifiers as {@link 
#parseId(String, String)}
+   * does.
+   *
+   * @param ids The identifiers, such as {@code seg-r1 seg-r2}.
+   * @param prefix The expected prefix of each identifier.
+   * @return The numbers in order.
+   * @throws IllegalArgumentException Thrown if {@code ids} is {@code null}, 
names no identifier, or
+   *         contains one that {@link #parseId(String, String)} rejects.
+   */
+  static int[] parseIds(String ids, String prefix) {
+    if (ids == null) {
+      throw new IllegalArgumentException("MASC identifier list must not be 
null");
+    }
+    String[] items = splitOnXmlWhitespace(ids);
+    if (items.length == 0) {
+      throw new IllegalArgumentException("MASC identifier list must name at 
least one identifier");
+    }
+    int[] numbers = new int[items.length];
+    for (int i = 0; i < items.length; i++) {
+      numbers[i] = parseId(items[i], prefix);
+    }
+    return numbers;
+  }
+
+  /**
+   * Splits an attribute value on runs of XML whitespace. Leading, trailing, 
and repeated separators
+   * produce no empty item.
+   *
+   * @param value The attribute value. Must not be {@code null}.
+   * @return The non-empty items in order; empty for a value without one.
+   */
+  private static String[] splitOnXmlWhitespace(String value) {
+    List<String> items = new ArrayList<>();
+    int start = -1;
+    for (int i = 0; i <= value.length(); i++) {
+      if (i == value.length() || isXmlWhitespace(value.charAt(i))) {
+        if (start >= 0) {
+          items.add(value.substring(start, i));
+          start = -1;
+        }
+      } else if (start < 0) {
+        start = i;
+      }
+    }
+    return items.toArray(new String[0]);
+  }
+
+  /**
+   * Parses the two ordered, nonnegative offsets of a contiguous text region.
+   *
+   * @param anchors The XML whitespace separated offsets.
+   * @return The region span.
+   * @throws IllegalArgumentException Thrown if the anchors are missing, 
malformed, out of range,
+   *         negative, or reversed.
+   */
+  static Span parseAnchors(String anchors) {
+    if (anchors == null) {
+      throw new IllegalArgumentException("MASC region anchors must not be 
null");
+    }
+    String[] items = splitOnXmlWhitespace(anchors);
+    if (items.length != 2) {
+      throw new IllegalArgumentException("MASC region anchors must contain 
exactly two offsets: " + anchors);
+    }
+    try {
+      return new Span(Integer.parseInt(items[0]), Integer.parseInt(items[1]));
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException("Invalid MASC region anchors: " + 
anchors, e);
+    }
+  }
+
+  /**
+   * Parses the ASCII digits from {@code from} to the end of {@code text} as a 
nonnegative
+   * {@code int}. A sign and digits of other scripts are not accepted.
+   *
+   * @param text The text holding the digits.
+   * @param from The index of the first digit.
+   * @return The number.
+   * @throws IllegalArgumentException Thrown if there is no digit at {@code 
from} or the digits
+   *         do not reach the end of {@code text}.
+   * @throws NumberFormatException Thrown if the number does not fit an {@code 
int}.
+   */
+  private static int parseAsciiInt(String text, int from) {
+    int end = StringUtil.endOfAsciiDigits(text, from);
+    if (end == from || end != text.length()) {
+      throw new IllegalArgumentException("Expected ASCII digits only: " + 
text.substring(from));
+    }
+    return Integer.parseInt(text, from, end, 10);
+  }
+
+  /**
+   * Tests for <a href="https://www.w3.org/TR/xml/#NT-S";>XML whitespace</a>: 
space, tab,
+   * carriage return or line feed.
+   *
+   * @param c The character to test.
+   * @return {@code true} if {@code c} is XML whitespace, {@code false} 
otherwise.
+   */
+  private static boolean isXmlWhitespace(char c) {
+    return c == ' ' || c == '\t' || c == '\r' || c == '\n';

Review Comment:
   Please declare `' '`, `'\t'`, `'\r'` and `'\n'` as constants, like in 
`EventFields`.



##########
opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascNamedEntityParserTest.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * 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.formats.masc;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.xml.sax.SAXException;
+
+public class MascNamedEntityParserTest {
+
+  /**
+   * Parses an annotation fixture.
+   *
+   * @param xml The annotation XML.
+   * @return The parser containing the annotations.
+   * @throws Exception Thrown if parsing fails.
+   */
+  private static MascNamedEntityParser parse(String xml) throws Exception {
+    return MascParserTestUtil.parse(xml, new MascNamedEntityParser());
+  }
+
+  /**
+   * Builds a person annotation for a node reference.
+   *
+   * @param ref The node reference.
+   * @return The annotation XML.
+   */
+  private static String entity(String ref) {
+    return "<a ref=\"" + ref + "\" label=\"person\"/>";
+  }
+
+  /**
+   * Checks that malformed annotations retain their validation cause.
+   *
+   * @param xml The malformed annotation XML.
+   */
+  private static void assertRejected(String xml) {

Review Comment:
   `assertRejected` and `parse` are copied into four test classes (here, 
`MascPennTagParserTest:57`, `MascSentenceParserTest:60`, 
`MascWordParserTest:59`). Please move them to `MascParserTestUtil`.



##########
opennlp-docs/src/docbkx/corpora.xml:
##########
@@ -844,4 +844,15 @@ Organization: precision:   85.11%;  recall:   79.38%; F1:  
 82.14%. [target: 130
                        </para>
                </section>
        </section>
+       <section xml:id="tools.corpora.masc">
+               <title>MASC</title>
+               <para>
+               The <link 
xlink:href="https://anc.org/data/masc/masc-structure";>MASC</link> reader 
requires
+               the identifiers <code>seg-r</code>, <code>penn-n</code> and 
<code>ne-n</code> followed by
+               ASCII digits, at most 2147483647, and exactly two nonnegative, 
ordered offsets per region.
+               Lists of identifiers and offsets are separated by space, tab, 
carriage return or line feed.
+               Since 3.0.0, a malformed annotation stops reading with an 
<code>IOException</code> whose
+               cause names the value.
+               </para>
+       </section>

Review Comment:
   The MASC section only lists validation rules. It doesn't say what MASC is or 
how to use the reader. "Since 3.0.0, a malformed annotation stops reading with 
an IOException" is also not right, it threw an `IOException` before, just 
without the cause. Either write a real section (what it is, which formats, one 
example) or drop it.



##########
opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascPennTagParserTest.java:
##########
@@ -0,0 +1,106 @@
+/*
+ * 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.formats.masc;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.xml.sax.SAXException;
+
+public class MascPennTagParserTest {
+
+  /**
+   * Parses an annotation fixture.
+   *
+   * @param xml The annotation XML.
+   * @return The parser containing the annotations.
+   * @throws Exception Thrown if parsing fails.
+   */
+  private static MascPennTagParser parse(String xml) throws Exception {
+    return MascParserTestUtil.parse(xml, new MascPennTagParser());
+  }
+
+  /**
+   * Builds a token node linked to segmentation regions.
+   *
+   * @param id The token identifier.
+   * @param targets The region identifiers.
+   * @return The annotation XML.
+   */
+  private static String tokenWithTargets(String id, String targets) {
+    return "<graph><node xml:id=\"" + id + "\"><link targets=\"" + targets + 
"\"/></node></graph>";
+  }
+
+  /**
+   * Checks that malformed annotations retain their validation cause.
+   *
+   * @param xml The malformed annotation XML.
+   * @return The parsing exception.
+   */
+  private static SAXException assertRejected(String xml) {
+    SAXException e = Assertions.assertThrows(SAXException.class, () -> 
parse(xml));
+    Assertions.assertInstanceOf(IllegalArgumentException.class, e.getCause());
+    return e;
+  }
+
+  @Test
+  void testTokenIdsLoseTheirPrefix() throws Exception {
+    MascPennTagParser parser = parse("<graph>"
+        + "<node xml:id=\"penn-n10\"><link targets=\"seg-r0 seg-r1\"/></node>"
+        + "<a ref=\"penn-n10\"><fs>"
+        + "<f name=\"msd\" value=\"NN\"/><f name=\"base\" value=\"test\"/>"
+        + "</fs></a>"
+        + "</graph>");
+    Assertions.assertArrayEquals(new int[] {0, 1}, 
parser.getTokenToQuarks().get(10));
+    Assertions.assertEquals("NN", parser.getTags().get(10));
+    Assertions.assertEquals("test", parser.getBases().get(10));
+  }
+
+  @ParameterizedTest
+  // a tab or a line break written directly into the attribute is a space 
after XML
+  // attribute-value normalization, a character reference keeps the character
+  
@MethodSource("opennlp.tools.formats.masc.MascParserTestUtil#xmlWhitespaceSeparators")
+  void testLinkTargetsUseXmlWhitespace(String separator) throws Exception {
+    MascPennTagParser parser = parse(tokenWithTargets("penn-n10", " seg-r0" + 
separator + "seg-r1 "));
+    Assertions.assertArrayEquals(new int[] {0, 1}, 
parser.getTokenToQuarks().get(10));
+  }
+
+  @ParameterizedTest
+  // these characters are not XML whitespace, even when introduced through a 
reference
+  @ValueSource(strings = {"seg-r0&#xA0;seg-r1", "seg-r0&#x3000;seg-r1", 
"seg-r0&#x85;seg-r1"})
+  void testLinkTargetsSeparatedByOtherWhitespaceAreRejected(String targets) {
+    assertRejected(tokenWithTargets("penn-n10", targets));
+  }
+
+  @ParameterizedTest
+  // doubled prefix, missing prefix, other prefix, no digits, trailing text
+  @ValueSource(strings = {"penn-npenn-n2", "2", "ne-n2", "penn-n", "penn-n2x"})
+  void testMalformedTokenIdsAreRejected(String id) {

Review Comment:
   The test checks the node id and the `ref`. Please split it.



##########
opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascIdentifiers.java:
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.formats.masc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import opennlp.tools.util.Span;
+import opennlp.tools.util.StringUtil;
+
+/**
+ * Parses MASC identifier and anchor attributes; lists are separated by
+ * <a href="https://www.w3.org/TR/xml/#NT-S";>XML whitespace</a>.
+ */
+final class MascIdentifiers {
+
+  /** The prefix of a named entity node identifier, as in {@code ne-n7}. */
+  static final String NAMED_ENTITY_ID_PREFIX = "ne-n";
+
+  /** The prefix of a Penn token node identifier, as in {@code penn-n7}. */
+  static final String PENN_TOKEN_ID_PREFIX = "penn-n";
+
+  /** The prefix of a segmentation region identifier, as in {@code seg-r7}. */
+  static final String REGION_ID_PREFIX = "seg-r";
+
+  private MascIdentifiers() {
+  }
+
+  /**
+   * Parses the number of an identifier that starts with {@code prefix} and 
continues with
+   * one or more ASCII digits only.
+   *
+   * @param id The identifier, such as {@code penn-n7}.
+   * @param prefix The expected prefix, such as {@link #PENN_TOKEN_ID_PREFIX}.
+   * @return The number after the prefix.
+   * @throws IllegalArgumentException Thrown if {@code id} is {@code null}, 
does not start with
+   *         {@code prefix}, is not followed by digits only, or the number 
does not fit
+   *         an {@code int}.
+   */
+  static int parseId(String id, String prefix) {
+    if (id == null || !id.startsWith(prefix)) {
+      throw invalidId(id, prefix, null);
+    }
+    try {
+      return parseAsciiInt(id, prefix.length());
+    } catch (NumberFormatException e) {
+      // reached only when the digits overflow an int
+      throw new IllegalArgumentException("MASC identifier number does not fit 
an int: " + id, e);
+    } catch (IllegalArgumentException e) {
+      throw invalidId(id, prefix, e);
+    }
+  }
+
+  /**
+   * Describes an identifier that is not {@code prefix} followed by ASCII 
digits.
+   *
+   * @param id The rejected identifier.
+   * @param prefix The expected prefix.
+   * @param cause The underlying error, or {@code null}.
+   * @return The exception to throw.
+   */
+  private static IllegalArgumentException invalidId(String id, String prefix, 
Throwable cause) {
+    return new IllegalArgumentException(
+        "MASC identifier must be " + prefix + " followed by digits: " + id, 
cause);
+  }
+
+  /**
+   * Parses an XML whitespace separated list of identifiers as {@link 
#parseId(String, String)}
+   * does.
+   *
+   * @param ids The identifiers, such as {@code seg-r1 seg-r2}.
+   * @param prefix The expected prefix of each identifier.
+   * @return The numbers in order.
+   * @throws IllegalArgumentException Thrown if {@code ids} is {@code null}, 
names no identifier, or
+   *         contains one that {@link #parseId(String, String)} rejects.
+   */
+  static int[] parseIds(String ids, String prefix) {
+    if (ids == null) {
+      throw new IllegalArgumentException("MASC identifier list must not be 
null");
+    }
+    String[] items = splitOnXmlWhitespace(ids);
+    if (items.length == 0) {
+      throw new IllegalArgumentException("MASC identifier list must name at 
least one identifier");
+    }
+    int[] numbers = new int[items.length];
+    for (int i = 0; i < items.length; i++) {
+      numbers[i] = parseId(items[i], prefix);
+    }
+    return numbers;
+  }
+
+  /**
+   * Splits an attribute value on runs of XML whitespace. Leading, trailing, 
and repeated separators
+   * produce no empty item.
+   *
+   * @param value The attribute value. Must not be {@code null}.
+   * @return The non-empty items in order; empty for a value without one.
+   */
+  private static String[] splitOnXmlWhitespace(String value) {
+    List<String> items = new ArrayList<>();
+    int start = -1;
+    for (int i = 0; i <= value.length(); i++) {
+      if (i == value.length() || isXmlWhitespace(value.charAt(i))) {
+        if (start >= 0) {
+          items.add(value.substring(start, i));
+          start = -1;
+        }
+      } else if (start < 0) {
+        start = i;
+      }
+    }
+    return items.toArray(new String[0]);
+  }
+
+  /**
+   * Parses the two ordered, nonnegative offsets of a contiguous text region.
+   *
+   * @param anchors The XML whitespace separated offsets.
+   * @return The region span.
+   * @throws IllegalArgumentException Thrown if the anchors are missing, 
malformed, out of range,
+   *         negative, or reversed.
+   */
+  static Span parseAnchors(String anchors) {
+    if (anchors == null) {
+      throw new IllegalArgumentException("MASC region anchors must not be 
null");
+    }
+    String[] items = splitOnXmlWhitespace(anchors);
+    if (items.length != 2) {
+      throw new IllegalArgumentException("MASC region anchors must contain 
exactly two offsets: " + anchors);
+    }
+    try {
+      return new Span(Integer.parseInt(items[0]), Integer.parseInt(items[1]));

Review Comment:
   ids only take ASCII digits, but the anchors go through `Integer.parseInt`. 
So `"+0 4"`, `"-0 +4"` and `"0 ٤"` are accepted, and `MascParserTestUtil:66-68` 
even tests for that. Please use `parseAsciiInt` for the anchors too and move 
the `equivalentAnchors` cases to the rejected ones.





> Regex removal (5c/10): Validate MASC identifiers and anchors consistently
> -------------------------------------------------------------------------
>
>                 Key: OPENNLP-1990
>                 URL: https://issues.apache.org/jira/browse/OPENNLP-1990
>             Project: OpenNLP
>          Issue Type: Improvement
>            Reporter: Kristian Rickert
>            Priority: Major
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> Unify MASC identifier, target-list, and anchor parsing across the relevant 
> readers. Validate expected prefixes, numeric ranges, and exactly two ordered, 
> nonnegative anchors for supported text spans, preserving useful SAX error 
> causes. Apply whitespace rules supported by the MASC/GrAF format and document 
> their relationship to XML attribute normalization: 
> https://www.w3.org/TR/xml/#AVNormalize. Preserve source text and offsets, add 
> malformed-input and Unicode regressions, and validate the changes against a 
> real MASC corpus.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to