Skip to content

Commit cb4660d

Browse files
committed
OPENNLP-1933: Parse event lines, MASC ids, and dictionary columns as intended
Review pass over the scans on this branch. Where a scan had copied a String.split or replaceFirst quirk, it now does what the format means: - Event lines: RealValueFileEventStream.parseEvent takes the outcome as the first whitespace separated field, shared with RealBasicEventStream. A tab or a run after the outcome no longer breaks the line, an outcome only line is an event without contexts instead of the end of the stream, and a blank line fails with an InvalidFormatException. - SimpleEventStreamBuilder splits the outcome at the first slash, so a context may contain one, and rejects an event without contexts. - MascIdentifiers.parseId requires the prefix at the start followed by ASCII digits only; parseIds parses link targets separated by whitespace runs. A doubled or missing prefix is reported instead of parsed. - FrequencyDictionaryLoader.splitColumns returns the non-empty columns only, so leading and repeated separators make no empty column. Tests no longer compare a scan with the pattern it replaced; they pin the accepted and rejected inputs directly. The manual describes the event file line format, the Leipzig file name rule, and the dictionary column separators.
1 parent 2d13a04 commit cb4660d

23 files changed

Lines changed: 381 additions & 158 deletions

File tree

opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/leipzig/LeipzigLanguageSampleStream.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444

4545
public class LeipzigLanguageSampleStream implements ObjectStream<LanguageSample> {
4646

47+
/** The number of leading file name characters that form the ISO 639-3 language code. */
48+
private static final int LANG_CODE_LENGTH = 3;
49+
4750
private class LeipzigSentencesStream implements ObjectStream<LanguageSample> {
4851

4952
private final String lang;
@@ -134,9 +137,6 @@ public LanguageSample read() throws IOException {
134137
}
135138
}
136139

137-
/** The number of leading file name characters that carry the ISO 639-3 language code. */
138-
private static final int LANG_CODE_LENGTH = 3;
139-
140140
private final int sentencesPerSample;
141141

142142
private final Map<String, Integer> langSampleCounts;

opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascIdentifiers.java

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717

1818
package opennlp.tools.formats.masc;
1919

20+
import opennlp.tools.tokenize.WhitespaceTokenizer;
21+
import opennlp.tools.util.StringUtil;
22+
2023
/**
2124
* Shared handling of the identifier attributes in MASC annotation files. Node, region,
22-
* and named entity identifiers carry a fixed text prefix followed by a number; the parsers
23-
* remove the prefix before parsing the number.
25+
* and named entity identifiers are a fixed text prefix followed by a number, as in
26+
* {@code penn-n7}; the parsers read the number and require the prefix.
2427
*/
2528
final class MascIdentifiers {
2629

@@ -37,19 +40,46 @@ private MascIdentifiers() {
3740
}
3841

3942
/**
40-
* Removes the first occurrence of {@code literal} from {@code input}. Later occurrences
41-
* stay in place, and {@code input} is returned unchanged if it does not contain
42-
* {@code literal}. The search is a plain text comparison.
43+
* Parses the number of an identifier that starts with {@code prefix} and continues with
44+
* one or more ASCII digits only.
4345
*
44-
* @param input The text to search. Must not be {@code null}.
45-
* @param literal The text to remove. Must not be {@code null}.
46-
* @return {@code input} without its first occurrence of {@code literal}.
46+
* @param id The identifier, such as {@code penn-n7}.
47+
* @param prefix The expected prefix, such as {@link #PENN_TOKEN_ID_PREFIX}.
48+
* @return The number after the prefix.
49+
* @throws IllegalArgumentException If {@code id} is {@code null}, does not start with
50+
* {@code prefix}, or is not followed by digits only.
4751
*/
48-
static String removeFirst(String input, String literal) {
49-
final int start = input.indexOf(literal);
50-
if (start < 0) {
51-
return input;
52+
static int parseId(String id, String prefix) {
53+
if (id == null || !id.startsWith(prefix) || id.length() == prefix.length()
54+
|| StringUtil.endOfAsciiDigits(id, prefix.length()) != id.length()) {
55+
throw new IllegalArgumentException(
56+
"MASC identifier must be " + prefix + " followed by digits: " + id);
57+
}
58+
return Integer.parseInt(id, prefix.length(), id.length(), 10);
59+
}
60+
61+
/**
62+
* Parses a whitespace separated list of identifiers, each as {@link #parseId(String, String)}
63+
* does.
64+
*
65+
* @param ids The identifiers, such as {@code seg-r1 seg-r2}.
66+
* @param prefix The expected prefix of each identifier.
67+
* @return The numbers in order.
68+
* @throws IllegalArgumentException If {@code ids} is {@code null}, names no identifier, or
69+
* contains one that {@link #parseId(String, String)} rejects.
70+
*/
71+
static int[] parseIds(String ids, String prefix) {
72+
if (ids == null) {
73+
throw new IllegalArgumentException("MASC identifier list must not be null");
74+
}
75+
String[] tokens = WhitespaceTokenizer.INSTANCE.tokenize(ids);
76+
if (tokens.length == 0) {
77+
throw new IllegalArgumentException("MASC identifier list must name at least one identifier");
78+
}
79+
int[] numbers = new int[tokens.length];
80+
for (int i = 0; i < tokens.length; i++) {
81+
numbers[i] = parseId(tokens[i], prefix);
5282
}
53-
return input.substring(0, start) + input.substring(start + literal.length());
83+
return numbers;
5484
}
5585
}

opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascNamedEntityParser.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ public void startElement(String uri, String localName, String qName, Attributes
5353

5454
try {
5555
if (qName.equals("a")) {
56-
int entityID = Integer.parseInt(
57-
MascIdentifiers.removeFirst(attributes.getValue("ref"), MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
56+
int entityID = MascIdentifiers.parseId(attributes.getValue("ref"),
57+
MascIdentifiers.NAMED_ENTITY_ID_PREFIX);
5858
String label = attributes.getValue("label");
5959
if (entityIDtoEntityType.containsKey(entityID)) {
6060
throw new SAXException("Multiple labels for one named entity");
@@ -64,10 +64,10 @@ public void startElement(String uri, String localName, String qName, Attributes
6464
}
6565

6666
if (qName.equals("edge")) {
67-
int entityID = Integer.parseInt(
68-
MascIdentifiers.removeFirst(attributes.getValue("from"), MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
69-
int tokenID = Integer.parseInt(
70-
MascIdentifiers.removeFirst(attributes.getValue("to"), MascIdentifiers.PENN_TOKEN_ID_PREFIX));
67+
int entityID = MascIdentifiers.parseId(attributes.getValue("from"),
68+
MascIdentifiers.NAMED_ENTITY_ID_PREFIX);
69+
int tokenID = MascIdentifiers.parseId(attributes.getValue("to"),
70+
MascIdentifiers.PENN_TOKEN_ID_PREFIX);
7171

7272
if (!entityIDsToTokens.containsKey(entityID)) {
7373
List<Integer> tokens = new ArrayList<>();

opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascPennTagParser.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,23 @@ public void startElement(String uri, String localName, String qName, Attributes
5555
try {
5656
//get the link between region and Penn tag
5757
if (qName.equals("node")) {
58-
tokenStack.push(Integer.parseInt(MascIdentifiers.removeFirst(
59-
attributes.getValue("xml:id"), MascIdentifiers.PENN_TOKEN_ID_PREFIX)));
58+
tokenStack.push(MascIdentifiers.parseId(
59+
attributes.getValue("xml:id"), MascIdentifiers.PENN_TOKEN_ID_PREFIX));
6060
}
6161

6262
if (qName.equals("link")) {
6363
if (tokenStack.isEmpty()) {
6464
throw new SAXException("The linking of tokens to quarks is broken.");
6565
}
6666

67-
String[] targets = attributes.getValue("targets")
68-
.replace(MascIdentifiers.REGION_ID_PREFIX, "").split(" ");
69-
70-
int[] regions = new int[targets.length];
71-
for (int i = 0; i < targets.length; i++) {
72-
int region = Integer.parseInt(targets[i]);
73-
regions[i] = region;
74-
}
67+
int[] regions = MascIdentifiers.parseIds(
68+
attributes.getValue("targets"), MascIdentifiers.REGION_ID_PREFIX);
7569
tokenToQuarks.put(tokenStack.pop(), regions);
7670
}
7771

7872
if (qName.equals("a")) {
79-
tokenStackTag.push(Integer.parseInt(MascIdentifiers.removeFirst(
80-
attributes.getValue("ref"), MascIdentifiers.PENN_TOKEN_ID_PREFIX)));
73+
tokenStackTag.push(MascIdentifiers.parseId(
74+
attributes.getValue("ref"), MascIdentifiers.PENN_TOKEN_ID_PREFIX));
8175
}
8276

8377
if (qName.equals("f")) {

opennlp-core/opennlp-formats/src/main/java/opennlp/tools/formats/masc/MascWordParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public void startElement(String uri, String localName, String qName, Attributes
4242
try {
4343
// create a word and put it into the list of words
4444
if (qName.equalsIgnoreCase("region")) {
45-
int id = Integer.parseInt(MascIdentifiers.removeFirst(
46-
attributes.getValue("xml:id"), MascIdentifiers.REGION_ID_PREFIX));
45+
int id = MascIdentifiers.parseId(
46+
attributes.getValue("xml:id"), MascIdentifiers.REGION_ID_PREFIX);
4747
String[] anchors = attributes.getValue("anchors").split(" ");
4848

4949
int left = Integer.parseInt(anchors[0]);

opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/leipzig/LeipzigLanguageSampleStreamTest.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,19 @@ void testReadSentenceFilesWithEmptyDir() {
100100
@ValueSource(strings = {"a", "eng", "dan", "abcdefghijklmnopqrstuvwxyz"})
101101
void testIsAsciiLowerCaseWordAccepts(String text) {
102102
Assertions.assertTrue(LeipzigLanguageSampleStream.isAsciiLowerCaseWord(text));
103-
Assertions.assertTrue(text.matches("[a-z]+"));
104103
}
105104

106105
@ParameterizedTest
107-
@ValueSource(strings = {"", "Eng", "eNg", "en1", "123", "e-g", "en ", " en", "\u00e9ng",
108-
"\u0130ng", "\uD835\uDC1Abc", "en\u00A0"})
106+
@ValueSource(strings = {"", "Eng", "eNg", "en1", "123", "e-g", "en_", "en ", " en", "\u00e9ng",
107+
"\u0130ng", "\uD835\uDC1Abc", "en\u00A0", "\uFF45ng"})
109108
void testIsAsciiLowerCaseWordRejects(String text) {
110109
Assertions.assertFalse(LeipzigLanguageSampleStream.isAsciiLowerCaseWord(text));
111-
Assertions.assertFalse(text.matches("[a-z]+"));
112110
}
113111

114112
@Test
115113
void testOnlyFilesWithLowerCaseAsciiLanguageCodesAreRead() throws IOException {
116114
String[] names = {"eng-sentences.txt", "Eng-sentences.txt", "en1-sentences.txt",
117-
"e-g-sentences.txt", "\u00e9ng-sentences.txt", "en"};
115+
"e-g-sentences.txt", "en_sentences.txt", "\u00e9ng-sentences.txt", "en"};
118116
for (String name : names) {
119117
Files.writeString(new File(emptyTempDir, name).toPath(),
120118
"1\tThis is a sentence.\n2\tThis is another sentence.\n", StandardCharsets.UTF_8);

opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascIdentifiersTest.java

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,62 @@
1717

1818
package opennlp.tools.formats.masc;
1919

20-
import java.util.stream.Stream;
21-
2220
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.Test;
2322
import org.junit.jupiter.params.ParameterizedTest;
24-
import org.junit.jupiter.params.provider.Arguments;
25-
import org.junit.jupiter.params.provider.MethodSource;
23+
import org.junit.jupiter.params.provider.CsvSource;
24+
import org.junit.jupiter.params.provider.ValueSource;
2625

2726
public class MascIdentifiersTest {
2827

29-
private static Stream<Arguments> removals() {
30-
return Stream.of(
31-
Arguments.of("ne-n7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "7"),
32-
Arguments.of("penn-n12", MascIdentifiers.PENN_TOKEN_ID_PREFIX, "12"),
33-
Arguments.of("seg-r0", MascIdentifiers.REGION_ID_PREFIX, "0"),
34-
Arguments.of("xne-n7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "x7"),
35-
Arguments.of("ne-nne-n7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "ne-n7"),
36-
Arguments.of("penn-n7penn-n", MascIdentifiers.PENN_TOKEN_ID_PREFIX, "7penn-n"),
37-
Arguments.of("seg-r", MascIdentifiers.REGION_ID_PREFIX, ""),
38-
Arguments.of("7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "7"),
39-
Arguments.of("", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, ""),
40-
Arguments.of("NE-N7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "NE-N7"),
41-
Arguments.of("ne\u2011n7", MascIdentifiers.NAMED_ENTITY_ID_PREFIX, "ne\u2011n7"),
42-
Arguments.of("\uD83D\uDE00ne-n1\uD83D\uDE00", MascIdentifiers.NAMED_ENTITY_ID_PREFIX,
43-
"\uD83D\uDE001\uD83D\uDE00"),
44-
Arguments.of("abc", "", "abc"));
28+
@ParameterizedTest
29+
@CsvSource({"ne-n7, ne-n, 7", "penn-n12, penn-n, 12", "seg-r0, seg-r, 0",
30+
"penn-n007, penn-n, 7", "ne-n2147483647, ne-n, 2147483647"})
31+
void testParseIdReadsTheNumberAfterThePrefix(String id, String prefix, int expected) {
32+
Assertions.assertEquals(expected, MascIdentifiers.parseId(id, prefix));
33+
}
34+
35+
@ParameterizedTest
36+
// other or missing prefix, prefix later in the text, doubled prefix, no digits, sign,
37+
// digits of another script, trailing text, whitespace, and an overflowing number
38+
@ValueSource(strings = {"7", "xne-n7", "NE-N7", "ne\u2011n7", "ne-nne-n7", "ne-n", "ne-n-7",
39+
"ne-n+7", "ne-n\u0661", "ne-n\uFF17", "ne-n7x", "ne-n7 ", " ne-n7", "ne-n7\n",
40+
"ne-n99999999999", ""})
41+
void testParseIdRejectsAnythingElse(String id) {
42+
Assertions.assertThrows(IllegalArgumentException.class,
43+
() -> MascIdentifiers.parseId(id, MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
44+
}
45+
46+
@Test
47+
void testParseIdRejectsNull() {
48+
Assertions.assertThrows(IllegalArgumentException.class,
49+
() -> MascIdentifiers.parseId(null, MascIdentifiers.NAMED_ENTITY_ID_PREFIX));
4550
}
4651

4752
@ParameterizedTest
48-
@MethodSource("removals")
49-
void testRemoveFirstRemovesOnlyTheFirstOccurrence(String input, String literal, String expected) {
50-
Assertions.assertEquals(expected, MascIdentifiers.removeFirst(input, literal));
51-
Assertions.assertEquals(input.replaceFirst(literal, ""), MascIdentifiers.removeFirst(input, literal));
53+
@ValueSource(strings = {"seg-r1 seg-r2", "seg-r1\tseg-r2", " seg-r1 seg-r2 ",
54+
"seg-r1\u00A0seg-r2", "seg-r1\u3000seg-r2", "seg-r1\r\nseg-r2"})
55+
void testParseIdsSplitsOnWhitespaceRuns(String ids) {
56+
Assertions.assertArrayEquals(new int[] {1, 2},
57+
MascIdentifiers.parseIds(ids, MascIdentifiers.REGION_ID_PREFIX));
58+
}
59+
60+
@Test
61+
void testParseIdsReadsASingleIdentifier() {
62+
Assertions.assertArrayEquals(new int[] {5},
63+
MascIdentifiers.parseIds("seg-r5", MascIdentifiers.REGION_ID_PREFIX));
64+
}
65+
66+
@ParameterizedTest
67+
@ValueSource(strings = {"", " ", "\t", "seg-r1 penn-n2", "seg-r1 seg-r", "seg-r1,seg-r2"})
68+
void testParseIdsRejectsEmptyOrMalformedLists(String ids) {
69+
Assertions.assertThrows(IllegalArgumentException.class,
70+
() -> MascIdentifiers.parseIds(ids, MascIdentifiers.REGION_ID_PREFIX));
71+
}
72+
73+
@Test
74+
void testParseIdsRejectsNull() {
75+
Assertions.assertThrows(IllegalArgumentException.class,
76+
() -> MascIdentifiers.parseIds(null, MascIdentifiers.REGION_ID_PREFIX));
5277
}
5378
}

opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascNamedEntityParserTest.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import org.junit.jupiter.api.Assertions;
2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.ValueSource;
2628
import org.xml.sax.SAXException;
2729

2830
import opennlp.tools.util.XmlUtil;
@@ -47,12 +49,25 @@ void testEntityAndTokenIdsLoseTheirPrefix() throws Exception {
4749
Assertions.assertEquals(List.of(4, 15), parser.getEntityIDsToTokens().get(3));
4850
}
4951

50-
@Test
51-
void testOnlyTheFirstPrefixOccurrenceIsRemoved() {
52+
@ParameterizedTest
53+
// doubled prefix, missing prefix, other prefix, no digits, trailing text
54+
@ValueSource(strings = {"ne-nne-n3", "3", "penn-n3", "ne-n", "ne-n3x"})
55+
void testMalformedEntityIdsAreRejected(String ref) {
5256
Assertions.assertThrows(SAXException.class, () -> parse(
53-
"<graph><a ref=\"ne-nne-n3\" label=\"person\"/></graph>"));
57+
"<graph><a ref=\"" + ref + "\" label=\"person\"/></graph>"));
58+
}
59+
60+
@ParameterizedTest
61+
@ValueSource(strings = {"penn-npenn-n4", "4", "seg-r4", "penn-n", "penn-n4x"})
62+
void testMalformedTokenIdsAreRejected(String to) {
5463
Assertions.assertThrows(SAXException.class, () -> parse(
5564
"<graph><a ref=\"ne-n3\" label=\"person\"/>"
56-
+ "<edge from=\"ne-n3\" to=\"penn-npenn-n4\"/></graph>"));
65+
+ "<edge from=\"ne-n3\" to=\"" + to + "\"/></graph>"));
66+
}
67+
68+
@Test
69+
void testMissingIdAttributeIsRejected() {
70+
Assertions.assertThrows(SAXException.class, () -> parse(
71+
"<graph><a label=\"person\"/></graph>"));
5772
}
5873
}

opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascPennTagParserTest.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import org.junit.jupiter.api.Assertions;
2424
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.ValueSource;
2527
import org.xml.sax.SAXException;
2628

2729
import opennlp.tools.util.XmlUtil;
@@ -48,11 +50,31 @@ void testTokenIdsLoseTheirPrefix() throws Exception {
4850
Assertions.assertEquals("test", parser.getBases().get(10));
4951
}
5052

51-
@Test
52-
void testOnlyTheFirstPrefixOccurrenceIsRemoved() {
53+
@ParameterizedTest
54+
@ValueSource(strings = {"seg-r0\tseg-r1", " seg-r0 seg-r1 ", "seg-r0\u00A0seg-r1",
55+
"seg-r0\nseg-r1"})
56+
void testLinkTargetsAreSeparatedByWhitespaceRuns(String targets) throws Exception {
57+
MascPennTagParser parser = parse("<graph>"
58+
+ "<node xml:id=\"penn-n10\"><link targets=\"" + targets + "\"/></node>"
59+
+ "</graph>");
60+
Assertions.assertArrayEquals(new int[] {0, 1}, parser.getTokenToQuarks().get(10));
61+
}
62+
63+
@ParameterizedTest
64+
// doubled prefix, missing prefix, other prefix, no digits, trailing text
65+
@ValueSource(strings = {"penn-npenn-n2", "2", "ne-n2", "penn-n", "penn-n2x"})
66+
void testMalformedTokenIdsAreRejected(String id) {
67+
Assertions.assertThrows(SAXException.class, () -> parse(
68+
"<graph><node xml:id=\"" + id + "\"><link targets=\"seg-r0\"/></node></graph>"));
5369
Assertions.assertThrows(SAXException.class, () -> parse(
54-
"<graph><node xml:id=\"penn-npenn-n2\"><link targets=\"seg-r0\"/></node></graph>"));
70+
"<graph><a ref=\"" + id + "\"><fs><f name=\"msd\" value=\"NN\"/></fs></a></graph>"));
71+
}
72+
73+
@ParameterizedTest
74+
// empty list, one malformed entry, other prefix, comma separated
75+
@ValueSource(strings = {"", " ", "seg-r0 seg-r", "seg-r0 penn-n1", "seg-r0,seg-r1"})
76+
void testMalformedLinkTargetsAreRejected(String targets) {
5577
Assertions.assertThrows(SAXException.class, () -> parse(
56-
"<graph><a ref=\"penn-npenn-n2\"><fs><f name=\"msd\" value=\"NN\"/></fs></a></graph>"));
78+
"<graph><node xml:id=\"penn-n2\"><link targets=\"" + targets + "\"/></node></graph>"));
5779
}
5880
}

opennlp-core/opennlp-formats/src/test/java/opennlp/tools/formats/masc/MascWordParserTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import org.junit.jupiter.api.Assertions;
2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.ValueSource;
2628
import org.xml.sax.SAXException;
2729

2830
import opennlp.tools.util.XmlUtil;
@@ -51,9 +53,11 @@ void testRegionIdsLoseTheirPrefix() throws Exception {
5153
Assertions.assertEquals(7, words.get(1).getEnd());
5254
}
5355

54-
@Test
55-
void testOnlyTheFirstPrefixOccurrenceIsRemoved() {
56+
@ParameterizedTest
57+
// doubled prefix, missing prefix, other prefix, no digits, trailing text
58+
@ValueSource(strings = {"seg-rseg-r3", "3", "penn-n3", "seg-r", "seg-r3x"})
59+
void testMalformedRegionIdsAreRejected(String id) {
5660
Assertions.assertThrows(SAXException.class, () -> parse(
57-
"<graph><region xml:id=\"seg-rseg-r3\" anchors=\"0 4\"/></graph>"));
61+
"<graph><region xml:id=\"" + id + "\" anchors=\"0 4\"/></graph>"));
5862
}
5963
}

0 commit comments

Comments
 (0)