Skip to content

OPENNLP-1930: Regex removal (2/10): Parse Arvores Deitadas markup with cursor scans - #1276

Merged
rzo1 merged 5 commits into
apache:mainfrom
ai-pipestream:OPENNLP-1930-ad-markup
Sep 20, 2026
Merged

rzo1 merged 5 commits into
apache:mainfrom
ai-pipestream:OPENNLP-1930-ad-markup

Conversation

@krickert

@krickert krickert commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Replaces the regular expressions in the Arvores Deitadas (AD) corpus readers with cursor-based character scans built on the StringUtil helpers. This PR also carries the AD reader changes that were split out of #1275.

The hyphen-splitting default change (splitHyphenatedTokens), ADDetokenizer, ADHyphenatedTokenTest and evalPortugueseTokenizerDefaultNaiveBayes are not in this PR. They moved to OPENNLP-1984, a separate stacked PR that will be opened after this one is merged.

Changes

  • ADSentenceStream: the markup patterns (<s>, </s>, <p>, <t>, </t>, <caixa>, </caixa>, <ext>, </ext>) are replaced by exact tag-name checks. SentenceParser reads node, leaf, bizarre-leaf and punctuation lines, and the fallback check, with cursor scans. The » + whitespace + ./, cleanup no longer uses a regex.
  • ADNameSampleStream: the NER tag, whitespace, underscore, hyphen and alphanumeric patterns are replaced by tagContent, StringUtil.splitOnUnicodeWhitespace, splitOnUnderscores, matchHyphenatedToken and isAlphaNumeric. getTextID reads its ids through ADMetadata.
  • ADPOSSampleStream: POS tags that contain whitespace are joined with = by replaceWhitespaceWithEquals instead of \s+.
  • ADSentenceSampleStream: the text and paragraph ids come from ADMetadata.parseTextAndParagraph instead of META_1.
  • New package-private ADMetadata: reads the text id, paragraph id, text prefix (LIT) and source attribute (CIE). An id that does not fit into an int counts as invalid metadata.
  • DetokenizerSampleStreamFactory: closes the detokenizer dictionary input stream after loading. Before this, JUnit @TempDir cleanup failed on Windows.
  • corpora.xml: documents the AD reader contract, including the whitespace definition, markup tags, the lemma boundary, tag-less tree lines, metadata ids and punctuation lines.

Behavior changes

The scans follow the format rather than quirks of the old patterns:

  • Quoted lemma boundary: a quoted lemma ends at the first matching quote that is followed by whitespace, a secondary tag or a closing bracket. The old greedy patterns backtracked to later quotes, and the new scan does not. Example: =H:n("x" M S)<tab>a') b now gives lemma x, morphology M S and lexeme a') b. Before, it gave lemma x" M S)<tab>a, no morphology and lexeme b. If the rest of the line does not parse after that quote, the line becomes a fallback leaf. Quotes inside a word stay part of the lemma.
  • Tree lines without a tag: a tree line that has no tag keeps its full lexeme, and the first lexeme character is no longer dropped.
  • Other parsing fixes: the tail of a node line that has parentheses inside a group is now read, and so are tags with quoted brackets.
  • Whitespace: whitespace is the Unicode White_Space property (including NBSP, U+0085, U+2028 and U+2029), independent of the toolkit whitespace mode.
  • Tag names: markup tags must match by exact name. <sx> and a self-closing <s/> are not sentence tags.
  • Punctuation lines: a punctuation line has no letter or digit of any script. An accented or non-Latin word is no longer treated as punctuation.
  • Hyphen splitting: when hyphen splitting is enabled, combining marks stay attached to their letters.
  • Underscores: leading, trailing or repeated underscores in a lexeme no longer produce empty tokens.

Validation

  • AD tests at the head: ADSentenceStreamTest 248, ADMetadataTest 87, ADNameSampleStreamTest 84 test runs; all 472 tests in opennlp.tools.formats.ad pass.
  • ArvoresDeitadasEval on Bosque_CF_8.0 and FlorestaVirgem_CF_3.0: the verifyTrainingData hashes of the sentence samples are unchanged. The tree, name, POS and chunk streams differ in 2 Bosque and 86 Floresta samples. All of these differences come from the reader fixes above: tag fragments such as .vp and <ARGOpp no longer appear as lexemes, and tag-less tree lines keep their text (P: reads DP, CJT: reads CJT, and the =H==( and =H===( lines contribute ( again).
  • The tokenizer eval constants in ArvoresDeitadasEval are unchanged in this PR.

OPENNLP-1930

rzo1

This comment was marked as outdated.

@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from f2fbb71 to beac8cd Compare September 15, 2026 16:00
@rzo1

This comment was marked as outdated.

1 similar comment
@rzo1

rzo1 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Here are some additional comments. We ran all AD streams (sentence, name, POS, chunk, raw tree, detokenized tokens) over the real Bosque_CF_8.0 and FlorestaVirgem_CF_3.0 corpora with main, #1275 and this PR. This PR changes nothing there: every stream is byte-identical to #1275, and the verifyTrainingData hashes in ArvoresDeitadasEval stay the same. The tokenizer eval input does change, but that comes from #1275 (see below). The divergences are all in markup and whitespace the corpora don't contain, and the PR doesn't document them. Def. still needs the eval build before merge.

Blocking

  1. PR description. It says the parsers "accept the same lines as the patterns they replace", with ~911k fuzzed lines and "500,000 for the metadata, with zero differences", and that "the line-terminator rule of the original patterns … is restored". None of that holds at the head: commit 5795ab2 added hasLineTerminator, beac8cd removed it again, and 7a8ebb4/beac8cd67 change behavior on purpose. OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275 vs this PR, ASCII-only fuzz: 3,968 differences in 5 kinds:

    • A. A whitespace-only lexeme (=a=a() ) is no longer a Leaf. Documented and tested.
    • B. \w became Character.isLetterOrDigit in the fallback check, so =-é. is now null and the word is dropped. Tested, not documented.
    • C. Whitespace follows the global mode (see 3).
    • D. The fallback check now reads past line terminators. Tested and documented.
    • E. Tag matching: <sx>, <tx>, <title>, <pp>, <caixa2>, <exta> and <situacao id="2"> now parse differently (<tx>/<title> shift sentence samples 143–150 times in 30k mutated documents; <situacao> in place of <s ...> changes all streams). <s/> and <p/> matched <s[^>]*> before and are rejected now, untested and undocumented.

    Also, lines that threw SIOOBE and lines that parsed have partly swapped. Rewrite the description to list these changes and the corpus results above. The test counts are off too ("ADMetadataTest adds 61": there are 63, and 22 cases removed from ADSentenceSampleStreamTest go unmentioned).

  2. ADMetadata.java:98-122, ADMetadataTest.java:55/:129. Metadata containing U+0085/U+2028/U+2029 used to throw "Invalid metadata" and is now read (3M fuzz inputs: 0 differences without a line terminator; with one, 86,262 / 24,227 / 52,206, all old reject → new accept, e.g. 12 p=1<U+2028>). The test comment "the reader does not produce one inside a line" is wrong: BufferedReader.readLine only splits on \n/\r, and an ISO-8859-1 byte 0x85 in a SOURCE: line reaches meta. Pick one behavior on purpose. Either restore the rejection, or keep the lenient version, fix the comment, and add a stream-level test with U+0085 in SOURCE:.

  3. ADSentenceStream.java:528, :564, :657, :695, :932. Tree and markup parsing depend on opennlp.whitespace.mode (OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275 blocking 5, still open and now wider):

    • An ISO-8859-1 cp1252 ellipsis byte 0x85 becomes U+0085: ==PU:pu("<U+0085>" PU)\t<U+0085> is a Node under UNICODE (token lost) and a Leaf under LEGACY.
    • ==H:n("casa" F S)<U+0085>casa parses under UNICODE and becomes a junk fallback leaf under LEGACY; …)<U+001C>casa and =S:np<U+001C> do the reverse.
    • NBSP in a functional tag: =H:n<NBSP>("o" M S)\tx was a leaf with func n<NBSP>; now a fallback leaf with lexeme :n<NBSP>("o" M S)\tx.
    • =S:n<U+2028>p was a node and is now a fallback leaf whose junk token reaches the name samples (X:p<U+2028>ar <START:organization> Editora …).
    • Trailing U+2028 or NBSP is stripped from node tags.

    corpora.xml:458-459 ("A line separator or next line character inside a line is read as an ordinary character") is therefore wrong, and so is the test comment at :103. Exclude line terminators in scanTag, isTagGroupRun and skipWhitespace, or use a fixed predicate. Document the mode dependency (corpora.xml:443 says "Unicode whitespace"), and add tests in both modes for U+0085 as lexeme, NBSP in a tag, U+2028 and U+001C.

  4. Eval build. Run ArvoresDeitadasEval before merge and list it as done:

Minor

  • ADSentenceStream.java:925-935, ADSentenceStreamTest.java:309-338. Add rows for <s/>, <p/>, <t/> and <s<U+0085>id>, and say in corpora.xml:454-457 that a self-closing tag is not a tag.
  • corpora.xml:448-450. Kind B (a fallback lexeme starting with a letter of any script is ignored) is tested but not documented. Add one sentence. :447-448 still says "the first run of digits" (OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275); the text id follows the leading ASCII letters and hyphens directly. :455 is overlong.
  • ADSentenceStream.java:446-461. The bizarre-leaf lemma loop runs indexOf(')') again for every quote and hyphen candidate, so it is cubic: "=" + "-"*4000 + "=y(\"" + "\""*4000 + ")" takes 2.0 s. The old regex took 163 s, so it's not a regression, but compute the first ) once, as parseLeaf does with noRestAt.
  • ADSentenceStream.java:370. new boolean[line.length() + 1] is allocated for every non-node line before the (+quote check at :391. Allocate it lazily.
  • ADSentenceStream.java:349/371/427/492-505, :591, :625, :644; ADMetadata.java:42-49, :98-122; ADSentenceSampleStream.java:138-143. Arrays used as tuples again (tag[0], rest[0..4], int[]{text, para}, a 4-slot span array, two arrays per sentence). Use private records. scanLevelAndTag recomputes the prefix run on every call, and :403 searches again for the separator scanTag already found.
  • ADSentenceStream.java:391, :447, :527, :543-544, :610-612, :639, :678, :708. '(', ')', '<', '>' and quotes are literals while TAG_SEPARATOR/TAG_GROUP_OPEN/TAG_GROUP_CLOSE (:66-69) are constants. Either drop the constants or declare all of them.
  • ADSentenceStream.java:381-388, :478-491, :580-604. The @return texts describe array slots, and "indexes after which no rest was found" is unexplained jargon. Make them concrete once records replace the arrays.
  • ADSentenceStream.java:313-314 (pre-existing). line.substring(level + 1) throws SIOOBE for a fallback line ending in =; parse() swallows it and returns null, which ends read() early. This PR changes which lines reach it (====N<:adj'a'b <<a> )<U+2028>= was a Node and now throws). Guard it while touching this code.
  • ADSentenceStream.java:905-910 (pre-existing, all versions). read() loops forever when an opening <s …> has no content before EOF. Worth a JIRA.
  • ADMetadata.java:39-41. parseTextAndParagraph throws NumberFormatException for a digit run over Integer.MAX_VALUE (so does getTextID:557). Add @throws, or map it to "Invalid metadata".
  • ADNameSampleStream.java:538-550 (pre-existing). textIdMeta2/textMeta2 are locals, so the LIT/CIE branch always returns 0 (or -1 for source="") and clearAdaptiveData never fires for LIT/CIE. The rewrite keeps this dead compare-and-increment. Either fix it with fields (separate JIRA, eval build) or reduce it to validation plus return 0 with a comment.
  • ADNameSampleStream.java:541-542, :552, :536. meta.startsWith("LIT") is evaluated twice, and "LIT"/"CIE" are bare literals; declare constants and branch once. // Amazonia explains nothing; add Javadoc to getTextID. Remove the blank line after the brace at :536.
  • Still open from OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275, please address here:
    • NER_PREFIX/magic 5 (ADNameSampleStream.java:501)
    • AIOOBE on splitOnUnderscores("_") (:265-269)
    • static helpers (ADNameSampleStream.java:375/401/425/458/478/497/511, ADPOSSampleStream.java:160, ADSentenceStream.java:925/:945)
    • duplicated anonymous ObjectStream helpers in ADSentenceSampleStreamTest.java:77-85 and ADNameSampleStreamTest.java:210-218 (use ObjectStreamUtils.createObjectStream)
    • bare RuntimeException asserts
  • ADMetadataTest.java. The move dropped reject cases "CF_1001 p=2", "CF 1001 p=2", "1001 p =2" and "1001 p= 2". Put them back.
  • Title. Implementation wording. Suggestion: "OPENNLP-1930: Read Arvores Deitadas tags by exact name and ignore whitespace-only lexemes".
  • Stacking. OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275 blocking 1 still applies: this PR sits on ccea670, which contains the first round of AD changes. Move those here.
  • Pre-existing, for a separate JIRA. ADChunkSampleStream throws NPE on FlorestaVirgem after 4,641 samples (the eval uses Bosque, so it is unaffected).

Verified:

@rzo1

This comment was marked as outdated.

@krickert krickert changed the title OPENNLP-1930: Parse Arvores Deitadas markup with cursor scans OPENNLP-1930: Read Arvores Deitadas tags by exact name and ignore whitespace-only lexemes Sep 16, 2026
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch 2 times, most recently from 7f14665 to dcc5e4c Compare September 16, 2026 06:09
@rzo1

rzo1 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

CI is red.

@rzo1 rzo1 changed the title OPENNLP-1930: Read Arvores Deitadas tags by exact name and ignore whitespace-only lexemes OPENNLP-1930: Regex removal (2/8): Parse Arvores Deitadas markup with cursor scans Sep 18, 2026
@rzo1 rzo1 changed the title OPENNLP-1930: Regex removal (2/8): Parse Arvores Deitadas markup with cursor scans OPENNLP-1930: Regex removal (2/10): Parse Arvores Deitadas markup with cursor scans Sep 18, 2026
@rzo1

rzo1 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

#1275 is split up and closed. The AD reader changes it carried (ADNameSampleStream, ADPOSSampleStream, ADSentenceStream, ADSentenceSampleStream, their tests and the AD paragraph of the corpora chapter) now belong to this PR, which already contains them. Please rebase onto #1311 (part 1) instead of the old #1275 branch.

krickert added a commit to ai-pipestream/opennlp that referenced this pull request Sep 18, 2026
The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition.

Hyphenated words are preserved by default; the ADTokenSampleStreamFactory
option and the CLI chapter describe the switch. A leaf without a
functional tag in ADChunkSampleStream is tagged with its lexeme, as the POS
reader does. The corpora chapter states the reader contract.

On the real corpora the output is unchanged: all AD streams over
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0 are byte-identical to the previous
readers, and the ArvoresDeitadasEval training data hashes are the same.

Squashed from the review rounds of apache#1276 for the rebase onto apache#1311.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from 2116ec0 to d7911d7 Compare September 18, 2026 12:11
@krickert

Copy link
Copy Markdown
Contributor Author

I'm running eval locally, if that's green I'll push

krickert added a commit to ai-pipestream/opennlp that referenced this pull request Sep 18, 2026
The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition.

Hyphenated words are preserved by default; the ADTokenSampleStreamFactory
option and the CLI chapter describe the switch. A leaf without a
functional tag in ADChunkSampleStream is tagged with its lexeme, as the POS
reader does. The corpora chapter states the reader contract.

On the real corpora the output is unchanged: all AD streams over
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0 are byte-identical to the previous
readers, and the ArvoresDeitadasEval training data hashes are the same.

Squashed from the review rounds of apache#1276 for the rebase onto apache#1311.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from d7911d7 to db074e8 Compare September 18, 2026 14:17
@rzo1

rzo1 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

I'm running eval locally, if that's green I'll push

Pleasae rebase this one on latest main. This will be next in the Regex queue.

@mawiesne mawiesne added java Pull requests that update Java code tests Pull requests that add or update test code labels Sep 18, 2026
krickert added a commit to ai-pipestream/opennlp that referenced this pull request Sep 18, 2026
The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition.

Hyphenated words are preserved by default; the ADTokenSampleStreamFactory
option and the CLI chapter describe the switch. A leaf without a
functional tag in ADChunkSampleStream is tagged with its lexeme, as the POS
reader does. The corpora chapter states the reader contract.

On the real corpora the output is unchanged: all AD streams over
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0 are byte-identical to the previous
readers, and the ArvoresDeitadasEval training data hashes are the same.

Squashed from the review rounds of apache#1276 for the rebase onto apache#1311.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from db074e8 to 2def6bf Compare September 18, 2026 18:42
@krickert
krickert marked this pull request as ready for review September 18, 2026 18:59
@rzo1

rzo1 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Thanks for the rebase. The branch is on current main, and the Windows fix (closing the detokenizer dictionary stream) looks right.

Blocking

  1. Hyphen-splitting default. splitHyphenatedTokens changes from true to false in ADNameSampleStreamFactory (and in all 8 cli.xml entries), and ADTokenSampleStreamFactory now wraps the detokenizer in the new ADDetokenizer. Anyone using -format ad for name finder or tokenizer training silently gets different training data. Nobody asked for this in OPENNLP-1928: Regex removal (1/8): Replace trivial patterns with character scans in StringUtil #1275, OPENNLP-1930: Regex removal (2/10): Parse Arvores Deitadas markup with cursor scans #1276, or OPENNLP-1928: Regex removal (1/10): Replace trivial patterns with character scans #1311, and it isn't regex removal. Please move it, together with ADDetokenizer, ADHyphenatedTokenTest, and evalPortugueseTokenizerDefaultNaiveBayes, into its own JIRA and PR with a release note.

  2. PR description and commit message.

    Please update both.

  3. Eval build. Please post the ArvoresDeitadasEval results for all tokenizer variants: Perceptron, GIS, QN, and NB. The description says the Perceptron value moves and that GIS, QN, and NB were not run, but the constants in the diff are unchanged. If the new NB test stays in some PR, its expected value (0.9951936406770416) needs a run that someone other than the author has checked.

  4. Please update the description as well.

Minor

  • The lemma scan now stops at the first quote that closes the field and no longer backtracks to later quotes like the old greedy pattern did. If the rest of the line doesn't parse after that quote, the line becomes a fallback leaf. That matches the documented intent and is fine, but please list it under the behavior changes in the description.

krickert added a commit to ai-pipestream/opennlp that referenced this pull request Sep 18, 2026
The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition. The corpora chapter states the reader
contract.

On the real corpora the output is unchanged: all AD streams over
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0 are byte-identical to the previous
readers, and the ArvoresDeitadasEval training data hashes are the same.

Squashed from the review rounds of apache#1276 for the rebase onto current main.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from 2def6bf to 3341ec2 Compare September 18, 2026 21:15
krickert added a commit to ai-pipestream/opennlp that referenced this pull request Sep 18, 2026
The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition. The corpora chapter states the reader
contract.

On the real corpora the sentence samples are unchanged: the
verifyTrainingData hashes of ArvoresDeitadasEval stay the same for
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0. The tree, name, POS and chunk
streams differ in 2 Bosque and 86 Floresta samples, all from the reader
bug fixes above: tag fragments such as .vp and <ARGOpp no longer appear
as lexemes, and the first character or the whole lexeme of a tag-less
tree line is no longer lost (P: reads DP, CJT: reads CJT, and the =H==(
and =H===( lines contribute ( again).

Squashed from the review rounds of apache#1276 for the rebase onto current main.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from 3341ec2 to 60cf48d Compare September 18, 2026 22:08
@krickert
krickert marked this pull request as draft September 19, 2026 07:24
@krickert

Copy link
Copy Markdown
Contributor Author

Moved this to draft. The split would be a stack, so I just need more time to think about it - it's not a big scope either - but I'll let it remain in draft for this reason.

It won't take long to do. I think this is mergable now but I won't flip it back until I test the new PR/JIRA. It's just a quick methodical way to do it. (it might only take an hour to fix though, so likely when everyone sees this message it'll probably be green anyway)

@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from 60cf48d to d556ac4 Compare September 19, 2026 12:39
@krickert
krickert marked this pull request as ready for review September 19, 2026 12:52
@krickert

Copy link
Copy Markdown
Contributor Author

Separation done. It's stacked, so I'll open a PR once this is merged - it's also ready to go https://github.com/ai-pipestream/opennlp/pull/new/OPENNLP-1984-ad-hyphen-default

I'll open the new one as soon as this is merged.

I'll do another pass of the remaining PRs. Please let me know if you catch any, let me know and I'll do the same surgery.

Thank you @rzo1 and @mawiesne for your patience on this - greatly appreciate it.

@rzo1
rzo1 requested a review from mawiesne September 19, 2026 16:24
@rzo1

rzo1 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Thanks for the split. The hyphen default, ADDetokenizer, ADHyphenatedTokenTest and the NB eval are out of this PR now, and I updated the description to match the code.

A few things are still open:

  1. Commit message. d556ac4 still says it adds evalPortugueseTokenizerDefaultNaiveBayes (F1 0.9951936406770416), but that test isn't in this PR anymore; it belongs with OPENNLP-1984. Please reword that commit since it goes into the squashed commit message.
  2. Unrelated docs change. corpora.xml adds a new "CoNLL-U" section (tools.corpora.conll.u) that has nothing to do with AD. It looks like it slipped in during the rebase. Please drop it or say where it belongs.
  3. Eval run. The eval tests are running on this head: https://ci-builds.apache.org/job/opennlp/job/eval-tests-configurable/81/

Once these are sorted, it's good from my side. @mawiesne, could you take a look as well?

The AD corpus readers no longer use regular expressions. This includes
the reader changes split out of OPENNLP-1928 (apache#1275): ADNameSampleStream,
ADPOSSampleStream, ADSentenceSampleStream and ADSentenceStream replace
their tag, whitespace, underline, hyphen, alphanumeric, metadata and
punctuation patterns with character scans over the StringUtil helpers.

ADSentenceStream.SentenceParser reads node, leaf and bizarre-leaf lines and
the fallback check with cursor scans, ADSentenceStream recognizes the
sentence, title, box, paragraph and text tags by name, and the
package-private ADMetadata reads the text id, the paragraph id and the
source for ADNameSampleStream and ADSentenceSampleStream. The scans follow
the format, not the accidents of the old patterns: an AD tree line without
a tag keeps its full lexeme, the first lexeme character is no longer lost,
paren-in-group node tails and quoted-bracket tags are read, and whitespace
follows the Unicode definition. The corpora chapter states the reader
contract.

On the real corpora the sentence samples are unchanged: the
verifyTrainingData hashes of ArvoresDeitadasEval stay the same for
Bosque_CF_8.0 and FlorestaVirgem_CF_3.0. The tree, name, POS and chunk
streams differ in 2 Bosque and 86 Floresta samples, all from the reader
bug fixes above: tag fragments such as .vp and <ARGOpp no longer appear
as lexemes, and the first character or the whole lexeme of a tag-less
tree line is no longer lost (P: reads DP, CJT: reads CJT, and the =H==(
and =H===( lines contribute ( again).

Squashed from the review rounds of apache#1276 for the rebase onto current main.
DetokenizerSampleStreamFactory.createDetokenizer left the dictionary
FileInputStream open, so JUnit TempDir cleanup failed on Windows for
ADHyphenatedTokenTest: 'detokenizer.xml: The process cannot access the
file because it is being used by another process' (27 errors, Windows
jobs of run 35343354338). The dictionary reads the stream fully in its
constructor, so close it there.
The review asked to put back the dropped reject cases 'CF_1001 p=2'
and 'CF 1001 p=2' (the text id follows the leading letters and hyphens
directly). Also pin U+2029, which the corpora chapter documents as
read like U+2028.
…ld boundary

The leaf scans searched backward from the end of the line, so a lemma
extended to the last quote after which the rest still parsed, the
greedy behavior of the replaced patterns: =H:n("x" M S)<tab>a') b
produced lemma 'x" M S)<tab>a', no morphology, and lexeme 'b'. Now the
first matching quote followed by whitespace, a secondary tag, or a
closing bracket closes the field, giving lemma 'x', morphology 'M S',
and lexeme 'a') b'. Later quotes are not tried, so a line that does not
parse after that quote becomes a fallback leaf. Quotes inside a word
remain part of the lemma, and the square bracket after some Floresta
time and measurement lemmas is accepted.

Red: 7 assertion failures over 247 parser cases. Passing: 500 focused
AD cases. No parsed field changed for the 920,203 quoted tree-line
candidates in Bosque and FlorestaVirgem. The manual documents the lemma
boundary rule.
@krickert
krickert force-pushed the OPENNLP-1930-ad-markup branch from d556ac4 to d3c2e06 Compare September 19, 2026 17:01

@rzo1 rzo1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@rzo1

rzo1 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

https://ci-builds.apache.org/job/OpenNLP/job/eval-tests-configurable/81/ failed. Needs an value update. Improvment:

ArvoresDeitadasEval.evalPortugueseChunkerPerceptron:190->chunkerCrossEval:138 expected: <0.9631066789979492> but was: <0.963736197429554>

…ected reader

Eval build 81 of eval-tests-configurable failed one assertion on this
branch: ArvoresDeitadasEval.evalPortugueseChunkerPerceptron expected
0.9631066789979492 and got 0.963736197429554. The other 14 Arvores
Deitadas runs, among them the GIS, QN and naive Bayes chunker runs,
passed at their expected values.

The change comes from 2 Bosque node lines with tags joined by a stray
separator, =====P.vp and ========N<ARGOpp. The old reader lost the
first character of a tag-less tree line and put .vp and <ARGOpp into
2 sentences as words of an NP chunk. The reader on this branch reads
them as tag-less lines with markup and adds no token. A dump of every
Bosque chunk sample from main and from this branch (4212 samples each)
shows no other difference than these 3 tokens.

ADChunkSampleStreamTest.testNodeLineWithStraySeparatorAddsNoToken
records that reading. Red on main: expected [que, teria, de, .] but was
[que, .vp, teria, <ARGOpp, de, .]. Passes on this branch.

The perceptron value is updated to the one the build got. Local runs
with the same Bosque file (md5 b69a3b82c385741cd876d166878b52e8)
reproduce 0.9631066789979492 on main and 0.963736197429554 on this
branch.
@krickert

Copy link
Copy Markdown
Contributor Author

@rzo1

rzo1 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

EVAL is FINE now.

@krickert

Copy link
Copy Markdown
Contributor Author

It's green again

@rzo1
rzo1 merged commit 9e7bee3 into apache:main Sep 20, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

java Pull requests that update Java code tests Pull requests that add or update test code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants