Section navigation

Token filters

Token filters receive tokens after tokenisation and can normalise, remove, replace, or add alternatives. Their order changes indexed terms, positions, offsets, and therefore query behaviour.

Build a pipeline

using Rowles.LeanCorpus.Analysis;
using Rowles.LeanCorpus.Analysis.Filters;
using Rowles.LeanCorpus.Analysis.Tokenisers;

var analyser = new Analyser(
    tokeniser: new Tokeniser(),
    new LowercaseFilter(),
    new AccentFoldingFilter(),
    new StopWordFilter(StopWords.English),
    new PorterStemmerFilter());

The example normalises before checking stop words and stems only the surviving terms.

Normalisation and rewriting

Filter Behaviour and configuration
LowercaseFilter Lowercases token text. Place before case-sensitive dictionaries.
AccentFoldingFilter Folds accented Latin characters to simpler forms. Decide whether the application needs originals as well.
DecimalDigitFilter Converts Unicode decimal digits to their ASCII equivalents.
ClassicFilter Removes English possessives and periods from uppercase acronyms such as U.S.A..
PatternReplaceFilter Applies a regular expression replacement to each token. Accepts a pattern and replacement, or a compiled Regex.
ReverseStringFilter Reverses token text, useful for suffix-oriented indexing. Query analysis must mirror it.
TruncateTokenFilter Limits each token to maxLength. Truncation can create collisions.
HyphenatedWordsFilter Recombines words split across hyphenated line endings. Configure the separator and use Finish by completing the analyser normally.
WordDelimiterFilter Splits punctuation, case, and letter-digit transitions. Controls include generated word and number parts, concatenation, original preservation, case splitting, numeric splitting, and possessive stemming.

Example compound-word configuration:

var delimiter = new WordDelimiterFilter
{
    GenerateWordParts = true,
    GenerateNumberParts = true,
    CatenateWords = true,
    SplitOnCaseChange = true,
    SplitOnNumerics = true,
    PreserveOriginal = true,
};

For WiFi4Schools_test, this can emit component terms and same-position alternatives. Inspect phrase behaviour when enabling several output forms.

Selection and limits

Filter Behaviour and configuration
StopWordFilter Removes the default or supplied stop-word set.
LengthFilter Keeps tokens between minLength and maxLength.
KeepWordFilter Keeps only tokens in the supplied set.
TypeTokenFilter Keeps or rejects configured token types through keepMatching.
UniqueTokenFilter Removes duplicate token text from one analysed stream.
LimitTokenCountFilter Emits at most maxTokenCount tokens. Prefer the writer token budget when the requirement is a document-wide safety policy.

Removing tokens must preserve meaningful position increments. Test phrase queries after adding a selection filter.

Stemming and protected terms

Filter Behaviour and configuration
PorterStemmerFilter Applies Porter stemming. Can share a KeywordMarkerFilter.
StemTokenFilter Wraps another ISpanStemmer. Can share a keyword marker.
HunspellStemFilter Uses a HunspellDictionary; injectAlternates controls whether alternatives are emitted.
KeywordMarkerFilter Marks supplied terms so compatible stemmers leave them unchanged.
var protectedTerms = new KeywordMarkerFilter(
    ["leancorpus", "dotnet"]);

var analyser = new Analyser(
    new Tokeniser(),
    new LowercaseFilter(),
    protectedTerms,
    new PorterStemmerFilter(protectedTerms));

See Hunspell for dictionary loading and limitations.

Synonyms, shingles, and token graphs

Filter Behaviour and configuration
SynonymGraphFilter Expands source phrases into alternate token-graph edges.
FlattenGraphFilter Converts graph edges to unit-length positions for postings. Required before indexing a graph-producing pipeline.
ShingleFilter Emits connected token n-gram graph edges. Configure minimum and maximum size, unigram output, and separator.
CommonGramsFilter Emits common-word bigrams using a supplied word set and separator.
var synonyms = new SynonymMap();
synonyms.Add("nyc", ["new", "york"]);

var analyser = new Analyser(
    new Tokeniser(),
    new LowercaseFilter(),
    new SynonymGraphFilter(synonyms),
    new FlattenGraphFilter());

Expansion increases postings and can change phrase positions. Keep synonym maps bounded and version them with the indexed corpus. Graph-producing filters require FlattenGraphFilter at index time; quoted queries retain graph paths and are bounded to prevent unbounded expansion.

Language and phonetic filters

Filter Behaviour and configuration
ElisionFilter Removes configured leading articles, with optional case-insensitive matching. Useful for languages with apostrophe elision.
MetaphoneFilter Emits a Metaphone encoding. inject controls whether the original is retained.
PhoneticAlternatesFilter Emits Latin-name phonetic alternatives. Configure inject and maxExpansions.

Phonetic expansion is a recall feature, not a replacement for language analysis. Put it on a dedicated field when exact spelling and phonetic matches need different boosts.

Diagnostics

CachingTokenFilter captures materialised Token values while forwarding the stream unchanged:

var capture = new CachingTokenFilter();
var analyser = new Analyser(
    new Tokeniser(),
    new LowercaseFilter(),
    capture);

capture.Reset();
analyser.Analyse("One TWO", sink);

foreach (var token in capture.Tokens)
    Console.WriteLine($"{token.Text} at {token.StartOffset}");

Captured text allocates strings and Clone() intentionally returns the same capture instance. Use it for inspection, not as an unnoticed production hot-path filter.

Character filters

Character filters transform the complete input before tokenisation:

Filter Use
HtmlStripCharFilter Removes HTML markup
MappingCharFilter Applies string mappings
PatternReplaceCharFilter Applies a regular expression replacement

Attach them through IndexWriterConfig.CharFilters. Offset-sensitive features need tests because changing source length can affect how offsets relate to original text.

Ordering checklist

  • Apply character filtering before tokenisation.
  • Normalise case and accents before lookup filters.
  • Mark protected keywords before stemming.
  • Expand synonyms after the normalisation expected by the synonym map.
  • Flatten graphs before a consumer that requires linear positions.
  • Put destructive limits after any expansions they are meant to bound.
  • Use equivalent index-time and query-time analysis.