Skip to content

OPENNLP-1974: Normalize complete Unicode emoji sequences - #1313

Merged
rzo1 merged 10 commits into
mainfrom
OPENNLP-1974-emoji-normalizer
Oct 1, 2026
Merged

rzo1 merged 10 commits into
mainfrom
OPENNLP-1974-emoji-normalizer

Conversation

@rzo1

@rzo1 rzo1 commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Replaces the character-range deletion in EmojiCharSequenceNormalizer with a code-point scanner that removes complete Unicode emoji sequences and leaves letters, CJK and text symbols in place.

  • The data is Unicode Emoji 17.0, the version of the other bundled Unicode files: 4,973 fully-qualified and minimally-qualified sequences and 10 component ranges in EmojiSequences.txt, generated by dev/UnicodeEmojiSequenceGenerator from the release files with pinned SHA-256 checksums and count. A minimally-qualified sequence lacks U+FE0F after a later element only and is removed like a fully-qualified one; an unqualified sequence, with no U+FE0F after the first symbol, is left as text. The Emoji 18.0 update is Update the bundled Unicode Emoji data to 18.0 #1347, for 3.1.
  • A stray joiner, modifier, selector, flag letter or tag keeps the emoji right before and after it; the other complete emoji in the run are removed. One redundant U+FE0F after a complete emoji is removed with it.
  • Under opennlp.compat.mode=LEGACY the normalizer reproduces the old output with a cursor scan: every hyphen, U+E000 to U+FFFF and nearly every supplementary character removed, BMP emoji such as U+231A kept. A characterization test compares it with the old pattern on 5,000 randomized inputs.
  • The class is no longer deprecated and is part of the default normalizer chain, so language detector features change in CURRENT mode. Affected models should be retrained.
  • Unicode License V3 attribution is in the source and binary distributions and the runtime jar.

OPENNLP-1974

@krickert

Copy link
Copy Markdown
Contributor

Unicode emoji file was missed on this, it's back now so it's working.

@krickert krickert changed the title OPENNLP-1974: Regex removal (8/10): Stop EmojiCharSequenceNormalizer from blanking hyphens and BMP characters OPENNLP-1974: Normalize complete Unicode emoji sequences Sep 21, 2026
Use a code-point matcher and pinned Unicode Emoji 18 data. Preserve non-emoji text and malformed candidates. Document training and inference implications.

Red: 6 of 8 intended-behavior checks failed on the former implementation; 11 Emoji 18 cases failed with the Emoji 17 inventory. Green: 50 focused tests pass, including all 3963 bundled sequences.
Append the Unicode V3 license and Emoji 18 source attribution to the runtime artifact metadata. Include the data license when the runtime JAR is distributed separately.
@krickert
krickert force-pushed the OPENNLP-1974-emoji-normalizer branch from dab9ea7 to 5ae9fbe Compare September 21, 2026 11:04

@krickert krickert 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.

From reviewing it, caught that Emoji v18 just came out last week. Includes the thumb innovations from the latest spec.

Comment thread opennlp-core/opennlp-runtime/src/main/appended-resources/META-INF/LICENSE Outdated
…vadoc

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rzo1

rzo1 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

@krickert, two questions about the bundled emoji data before we merge:

  1. Diff in the bundled data: The description originally described an Emoji 17.0 inventory of 3,944 sequences. The head now ships EmojiSequences-18.0.txt with 3,963 sequences, pulled from the Unicode 18.0.0 files with new pinned checksums and a 2026 copyright year. What's in those 19 extra sequences, and did anything else in the inventory or the 10 component ranges change between 17.0 and 18.0? Could you post the generator's 17.0 → 18.0 diff here so reviewers can check it?

  2. Scope: Should the Emoji 17 → 18 upgrade be in this PR at all? This is a behavior-fix ticket for EmojiCharSequenceNormalizer, and the spec bump went in without a mention in the description or the commit messages. It also leaves us on mixed versions, since WordBreakProperty, ExtendedPictographic and the emoji annotations are still on 17.0. I'd lean towards landing the scanner on 17.0 here and doing the 18.0 upgrade for all bundled Unicode data in a separate issue, so the language-detector evaluation measures only the normalizer change. Is there a reason it needs to be 18.0 in this PR?

@krickert
krickert marked this pull request as draft September 22, 2026 18:00
@krickert

Copy link
Copy Markdown
Contributor

@krickert, two questions about the bundled emoji data before we merge:

  1. Diff in the bundled data: The description originally described an Emoji 17.0 inventory of 3,944 sequences. The head now ships EmojiSequences-18.0.txt with 3,963 sequences, pulled from the Unicode 18.0.0 files with new pinned checksums and a 2026 copyright year. What's in those 19 extra sequences, and did anything else in the inventory or the 10 component ranges change between 17.0 and 18.0? Could you post the generator's 17.0 → 18.0 diff here so reviewers can check it?

I'll do a total audit to see what the dependency is. I remember seeing most of them were thumbs of multiple colors, but I'll double check

  1. Scope: Should the Emoji 17 → 18 upgrade be in this PR at all?

I think so, but totally not important (I don't think this is a heavily used feature, but will become an incredibly helpful one in the future).

But any pushback on emjois I won't battle :) No problem - 17 is coming back. Made it a draft for a quick sec to make sure the other nits are still in though...

I thought of it as a quick property update, but even that should be it's own PR. Noted.

@rzo1

rzo1 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

@krickert I would treat a version update 17 -> 18 similar to a "code" dependency version update, so it is trackable in the release notes and doesn't hide in a normalizer ticket :)

@rzo1

rzo1 commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@krickert Any updates here?

@krickert

krickert commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

\I had 2 left from last night before I stopped, this was one of 'em. This is gettng the split today, and a v18 branch will be created w/o a PR yet (assumed we'll do v18 for 3.1+). I should be able to get to it tonight / tomorrow AM

Use Unicode Emoji 17.0 data for emoji removal: 3,944 sequences and 10
component ranges. Pin the release, source checksums and sequence count
in the generator. Load the data lazily and reject malformed entries.

Keep emoji next to stray joiners, modifiers, selectors, flag letters or
tags, and remove other complete emoji in the same run. Remove one extra
U+FE0F after a complete emoji with that emoji. Skip non-emoji text before
the trie lookup.

Preserve the old output under opennlp.compat.mode=LEGACY with a cursor
scan instead of regex. Compare it with the old pattern over 5,000
randomized inputs, and test the normalizer chain in both compatibility
modes.

Remove the class deprecation and use it in the default normalizer chain.
Document the behavior in Javadoc and the language detector chapter, and
update the runtime jar LICENSE and NOTICE for the bundled Unicode data.

Assisted-by: Claude Code 2.1.281
Co-authored-by: Kristian Rickert <kristian@apache.org>
Co-authored-by: Claude <noreply@anthropic.com>
Reviewed-by: Kristian Rickert <kristian@apache.org>
Signed-off-by: Kristian Rickert <kristian@apache.org>
@krickert

Copy link
Copy Markdown
Contributor

Pushed the fixes and moved the data back to Emoji 17.0. The Emoji 18.0 update is tracked in #1347 for 3.1.

LEGACY mode keeps the old behavior without using regex. The tests compare its output against the old pattern.

Removed the deprecation and added the normalizer to the default chain. Emoji next to stray components are preserved; other complete emoji are removed, including one extra U+FE0F after them.

Updated the Javadoc and Unicode license notices, and moved the tests to opennlp-runtime.

Eval: https://ci-builds.apache.org/job/OpenNLP/job/eval-tests-configurable/91/console

@krickert
krickert marked this pull request as ready for review September 30, 2026 09:45
@rzo1 rzo1 added this to the 3.0.0 milestone Sep 30, 2026
- Trie nodes keep sorted primitive arrays instead of HashMap<Integer, Node>,
  so a lookup does not box.
- Lazy loading uses a holder class instead of double-checked locking.
- The normalizer checks the candidate start set before decoding a second time.
- Javadoc names the pre-3.0.0 regex once instead of "the former pattern".
- HexCodePoints no longer claims to be shared by the other data loaders.
- The generator reads each source file once and accepts a bare output name.
- The characterization tests are parameterized so a failing input is named.
- The docbook entry keeps two sentences in the table and moves the LEGACY note
  below it.
@rzo1
rzo1 force-pushed the OPENNLP-1974-emoji-normalizer branch from 0b4f549 to 69450f9 Compare September 30, 2026 18:29
… HexCodePoints

Confusables and EmojiAnnotations used their own Integer.parseInt(..., 16)
loops. They now use HexCodePoints like UnicodeEmojiSequences, so an out of
range code point fails with the line number instead of a bare exception
from appendCodePoint.
emoji-test.txt lists 1,029 minimally-qualified sequences, mostly gendered and
skin tone ZWJ sequences written without U+FE0F after a later element, such as
U+1F64B U+1F3FF U+200D U+2640. They were kept whole as malformed candidates.
The generator now keeps them next to the fully-qualified ones, so the
inventory holds 4,973 sequences. Unqualified sequences, whose first code
point lacks a needed U+FE0F, are still kept as text.
- UnicodeEmojiSequences: adding a sequence is an instance method of the trie
  node instead of a static helper taking the root.
- UnicodeEmojiSequenceGenerator: main creates an instance; the helpers are
  instance methods.
- Tests: helpers called only from test methods are instance methods.

Factories (getInstance, load, parse), the HexCodePoints utility, JUnit
method sources and the helpers they call stay static.
@rzo1

rzo1 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

@rzo1

rzo1 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Note for the 3.0.0 release notes: since CompatibilityMode defaults to CURRENT from 3.0.0 on, the EmojiCharSequenceNormalizer in the default language detector chain produces different features than the one langdetect-183.bin was trained with (hyphens, fullwidth forms, private use and non-emoji supplementary characters are no longer stripped). So langdetect-183.bin needs to be re-trained for 3.0.0 ;-)

Until then, users of the existing model can get the old features back with -Dopennlp.compat.mode=LEGACY.

Also worth noting: opennlp-eval-tests has no language detector eval, so the green eval run doesn't cover this change.

@rzo1
rzo1 requested a review from krickert September 30, 2026 18:50
The normalizer data loaders read code point sequences separated by any whitespace run,
with leading and trailing whitespace ignored, instead of single spaces only.
HexCodePoints tokenizes a sequence with StringUtil.splitOnUnicodeWhitespace, so the
confusables and annotation loaders and the emoji trie accept tabs and repeated spaces between
code points and ignore surrounding whitespace. The single space separator constant is gone.
The getInstance Javadoc of UnicodeEmojiSequences names the error the holder idiom produces.
@krickert

krickert commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

I did a minor change - only single whitespace (' ') was checked, changed it to apply to all unicode whitespace.

Sourceforge was down, which is why tests were red.

@rzo1
rzo1 merged commit ceb2bd8 into main Oct 1, 2026
16 of 28 checks passed
@rzo1
rzo1 deleted the OPENNLP-1974-emoji-normalizer branch October 1, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants