OPENNLP-1875: Align whitespace handling with the Unicode White_Space property#1150
OPENNLP-1875: Align whitespace handling with the Unicode White_Space property#1150krickert wants to merge 8 commits into
Conversation
c680c40 to
6fa614e
Compare
Characterization tests ahead of migrating the user-text whitespace predicates to the Unicode White_Space set. Every expectation in this commit captures the behavior of the current implementation; the sites classified for migration flip together with the code change in the follow-up commits, so the review diff shows the exact behavior delta. Audit of StringUtil.isWhitespace, Character.isWhitespace, Character.isSpaceChar and \s regex splits outside the OPENNLP-1850 engine (opennlp/tools/util/normalizer, opennlp/tools/tokenize/uax29), the four legacy CharSequenceNormalizers and the opennlp-dl module: Migrate to Unicode White_Space (user-text paths): * WhitespaceTokenizer.tokenizePos (opennlp-api) * SimpleTokenizer.tokenizePos (opennlp-runtime) * Span.trim (opennlp-api) * SentenceDetectorME.isAcceptableBreak whitespace guard before an abbreviation (opennlp-runtime) * StopwordFilterTool stdin line split (opennlp-cli) * SymSpell.lookupCompound term split (opennlp-spellcheck) * SpellCheckingCharSequenceNormalizer PER_TOKEN boundaries (opennlp-spellcheck) * CorrectTextTool -suggest token split (opennlp-spellcheck) * NumberUtil.parse whitespace strip (opennlp-uima) Keep frozen, feature generation for existing trained models or a reference-fixed definition (pinned permanently in this commit): * DefaultSDContextGenerator sp/sn features (opennlp-runtime) * DefaultTokenContextGenerator _ws feature (opennlp-runtime) * BertNormalization.isWhitespace, fixed by the reference BERT implementation (opennlp-api) * StringUtil.isWhitespace itself: after the migration its remaining in-tree callers are exactly the frozen feature generators and the format parsers below, so the predicate keeps its current semantics and the user-text call sites move to the Unicode set instead Keep, config/format parsing where JVM or ASCII-regex semantics belong to the format rather than to user text: * ConlluTokenSampleStream text comment alignment (CoNLL-U) * SgmlParser tag and attribute scanning (MUC SGML) * TokenSampleStream lang/en (PTB-style training format) * DictionaryStopwordFilter stopword list files * DownloadUtil checksum file split * ADNameSampleStream, ADPOSSampleStream, ADSentenceStream (AD corpus) * NameSample START_TAG_PATTERN and Parse tokenPattern (annotation formats) * RegexNameFinderFactory \s inside structured entity patterns (regex-by-design component) * StringTokenizer sites: Dictionary, HeadRules, AncoraSpanishHeadRules, AbstractModelReader, FileEventStream, GenerateManualTool (model and resource file formats) * Test-only \s+ splits of ASCII fixtures
…sers StringUtil.isWhitespace keeps its exact current semantics (the union of Character.isWhitespace and the Zs category). The audit showed that after the user-text migration its remaining in-tree callers are exactly the sites that must not change: * DefaultSDContextGenerator and DefaultTokenContextGenerator generate feature strings that are baked into every trained sentence-detector and tokenizer model; moving them to the Unicode White_Space set would silently change features for existing models. Both now carry an intentional-exception note and are pinned by tests. * The format parsers (CoNLL-U alignment, MUC SGML markup, stopword list files, PTB-style training data, AD corpus fields, checksum files, the <START:type> annotation markup, the PTB bracket grammar) parse fixed formats where ASCII or JVM whitespace is the format's own definition. Each retained site now carries a one-line justification. * BertNormalization.isWhitespace is fixed by the reference BERT implementation; pre-trained vocabularies depend on it. * RegexNameFinderFactory applies regex to text by design; the embedded \s classes are part of the entity grammar. The StringUtil javadoc now states the frozen definition, its exact deltas against the Unicode White_Space property (includes U+001C.. U+001F, excludes U+0085), the model-stability rationale, and points user-text code at UnicodeWhitespace.isWhitespace / CharClass.whitespace as the standards-sourced alternative delivered by OPENNLP-1850. No behavior change in this commit.
Move the user-text whitespace predicates of the rule-based components onto the standards-sourced Unicode White_Space set delivered by OPENNLP-1850 (UnicodeWhitespace/CharClass), generalizing the OPENNLP-205 sentence-detector correction: * WhitespaceTokenizer.tokenizePos (opennlp-api) * SimpleTokenizer.tokenizePos (opennlp-runtime) * Span.trim (opennlp-api) * SentenceDetectorME.isAcceptableBreak: the whitespace guard before an abbreviation now uses the detector-wide WHITESPACE CharClass instead of Character.isWhitespace, so all detector stages share one whitespace definition Behavior delta, pinned by the flipped characterization tests: the next line control U+0085 now counts as whitespace (separates tokens, is trimmed from spans, protects an abbreviation), and the U+001C..U+001F information separators no longer do. For the abbreviation guard the no-break spaces (U+00A0, U+2007, U+202F) also start counting as whitespace, because that site previously used the narrower Character.isWhitespace. NBSP handling in the tokenizers and Span.trim is unchanged (the legacy predicate already included the Zs category). Note for existing models: WhitespaceTokenizer output feeds TokenizerME candidate spans and BPETokenizer pre-tokenization, so tokens produced for text containing the affected code points can differ from earlier releases. Feature generation itself stays on the frozen legacy predicate (see the previous commit).
…pace
Replace the ASCII \s regex whitespace handling on the remaining
user-text paths with the standards-sourced Unicode White_Space set from
OPENNLP-1850:
* StopwordFilterTool and CorrectTextTool -suggest tokenize input lines
with WhitespaceTokenizer (now White_Space-backed) instead of
split("\\s+"); this also drops the empty leading token the regex
produced for lines starting with whitespace
* SymSpell.lookupCompound splits terms with a cursor scan on
UnicodeWhitespace instead of trim().split("\\s+")
* SpellCheckingCharSequenceNormalizer PER_TOKEN boundaries move from
Character.isWhitespace to UnicodeWhitespace (whitespace runs are
still copied verbatim, so offsets are preserved)
* NumberUtil.parse (opennlp-uima) strips whitespace with a cursor scan
on UnicodeWhitespace instead of a \s regex pass, so the no-break
spaces several locales use as digit grouping separators no longer
stop the number parse
Behavior delta, pinned by the flipped characterization tests: for the
regex sites the change is purely additive (NBSP, figure/narrow space,
NEL, the Unicode line and paragraph separators and the other Zs
separators now separate or strip; U+001C..U+001F never matched \s and
still do not split). For the PER_TOKEN normalizer, which used
Character.isWhitespace, U+001C..U+001F additionally stop being token
boundaries. None of these components feeds trained-model features.
6fa614e to
676e5de
Compare
|
Hi @krickert - as mentioned on Slack I currently dont have the time for a manual review but I just let Fable do a comprehensive review on this PR. Here is the result:
Human review will follow. |
…tors, spellcheck consistency The sample parsers that split annotation lines through the shared WhitespaceTokenizer (NameSample, POSSample, TokenSample, the brat, OntoNotes and doccat streams) now carry the same per-site justification comment as the frozen direct call sites, and characterization tests pin that a U+0085 separated line parses identically to its ASCII-space equivalent, spans included. keepNewLines now emits the next line control U+0085 and the Unicode line and paragraph separators as newline tokens instead of silently dropping them, in both WhitespaceTokenizer and SimpleTokenizer, with the emitted set documented in the javadoc; the SimpleTokenizer path also anchors the emitted span at the separator's own position. SymSpell's compound distance now trims with the same Unicode set it splits with, so a leading or trailing NBSP no longer inflates the reported distance, and the spellcheck command line emits an empty line for a whitespace-only input line instead of swallowing it. SentenceDetectorME.isAcceptableBreak and NumberUtil.parse carry the same since-3.0 Unicode White_Space note as the other migrated public sites. Tests cover each changed behavior.
|
Thanks. Addressed in 9fd57a8, one pushback:
|
There was a problem hiding this comment.
A) Please reduce the inline commentary.
Nearly every touched call site carries a multi-line narrative comment ("Sample parsing deliberately shares…", "Resource-format parsing: …", "Format parsing: the training data is…", the restated whitespace-delta notes in SentenceDetectorME, SymSpell, NumberUtil, etc.). These explain the migration decision, not the code.
IMHO that rationale belongs in the JIRA issue / this PR description / the release notes, where it's already captured. In the code it's noise that will go stale and doesn't help the next reader. (This can be taken verbatim to all other open PRs as well)
Concretely:
- Drop the per-call-site justification comments entirely (format parsers, sample parsers, CLI tools, isLineSeparator, the repeated "since 3.0…" delta enumerations at call sites).
- Keep, but condense to 1–3 lines, the notes that document a real invariant or user-visible behavior change: the frozen-predicate rationale on StringUtil.isWhitespace and the two context generators, the BERT-reference pin in BertNormalization, and a short "whitespace is the Unicode White_Space set since 3.0" note in the javadoc of WhitespaceTokenizer, SimpleTokenizer and Span#trim. One sentence plus a pointer to OPENNLP-1875 is enough; the full U+xxxx enumerations don't need to live in javadoc.
- The comment explaining the SimpleTokenizer span-position fix in keepNewLines mode can stay (it explains a non-obvious bug fix), but one line suffices.
B) Please expose the new predicate via StringUtil and use that at the migrated call sites.
I'd like to avoid splitting the whitespace predicates across two entry points: people reach for StringUtil by habit, and having the legacy and the standard predicate side by side (with distinct names) makes the difference discoverable. UnicodeWhitespace should stay as the implementation/reference-data home; just add a delegating facade:
/**
* Unicode {@code White_Space} membership; delegates to
* {@link opennlp.tools.util.normalizer.UnicodeWhitespace#isWhitespace(int)}.
*/
public static boolean isUnicodeWhitespace(int codePoint) {
return UnicodeWhitespace.isWhitespace(code
}
(plus a char overload to mirror the existing pair), and switch the migrated call sites (WhitespaceTokenizer, SimpleTokenizer, Span#trim, spellcheck, UIMAdeWhitespace.isWhitespace(…) toStringUtil.isUnicodeWhitespace(…). The CharClass.whitespace() usage in SentenceDetectorME is fine as is.
C) I think the issue and PR would benefit from a clearer title / heading
The PR title (and the issue title) is hard to understand; something like
- Align whitespace handling with the Unicode White_Space property
- Migrate whitespace checks on user text to Unicode White_Space
would be better understandable and cleaner in release notes.
…ences Applies the review conventions from the OPENNLP-1869 review: frozen-predicate notes shrink to one contract sentence, JIRA keys and design narrative leave the sources, and the private helpers gain javadoc.
UCD-backs the whitespace predicates outside the OPENNLP-1850 engine (OPENNLP-1852 consistency item; generalizes the OPENNLP-205 correction). Every
StringUtil.isWhitespace,Character.isWhitespace,Character.isSpaceChar, and\s-split site outside the 1850 engine was audited and classified:White_Spaceset: both rule-based tokenizers,Span.trim, the sentence detector's abbreviation guard, the stopword and spellcheck CLIs,SymSpell.lookupCompound, the spellcheckPER_TOKENnormalizer, and the UIMANumberUtil.StringUtil.isWhitespaceitself stays frozen as the documented legacy predicate, since its remaining callers are exactly the frozen sites; user-text code is directed toUnicodeWhitespace.Method is characterize-then-refactor: the first commit pins pre-change behavior at NBSP, figure and narrow space, U+2028/U+2029, NEL, and U+001C..U+001F for every touched site, so the migration commits flip only the deliberate deltas.
This is a behavior change; the release-note text is in the JIRA issue.
Verification: opennlp-runtime 1313/0, plus green suites in api, formats, cli, tools, uima, and spellcheck.
https://issues.apache.org/jira/browse/OPENNLP-1875