Gliner/Preprocessing/GlinerTextPreprocessor.cs
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace GlinerPoc.Preprocessing;
/// <summary>
/// Exact port of gliner2's WhitespaceTokenSplitter (Phase 3.6): the GLiNER
/// word pre-processing stage that runs BEFORE subword tokenization.
///
/// Official pattern (Python re, VERBOSE | IGNORECASE):
/// (?:https?://[^\s]+|www\.[^\s]+)
/// |[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}
/// |@[a-z0-9_]+
/// |\w+(?:[-_]\w+)*
/// |\S
/// each yield is (token.lower(), start, end).
///
/// Python \w = Unicode alphanumerics (L*, Nd, Nl, No) + underscore; the C#
/// class [\p{L}\p{N}_] is the same set. Known documented divergence: Python
/// \s additionally matches the file-separator control range U+009C-009F
/// region characters that .NET \s does not (irrelevant for V1 English scope;
/// recorded in the architecture doc).
/// </summary>
public static class GlinerTextPreprocessor
{
private static readonly Regex Pattern = new(
"(?:https?://[^\\s]+|www\\.[^\\s]+)" +
"|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}" +
"|@[a-zA-Z0-9_]+" +
"|[" + "\\p{L}\\p{N}_" + "]+(?:[-_][" + "\\p{L}\\p{N}_" + "]+)*" +
// Surrogate pairs must match as ONE token: Python \S operates on whole
// code points, .NET \S on UTF-16 code units (emoji would split).
"|(?:[\\uD800-\\uDBFF][\\uDC00-\\uDFFF])" +
"|\\S",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary>
/// Official context rule: append "." when the text does not end with
/// '.', '!' or '?'; an empty context becomes ".".
/// </summary>
public static string ApplyContextRule( string text )
{
if ( string.IsNullOrEmpty( text ) )
{
return ".";
}
char last = text[^1];
if ( last == '.' || last == '!' || last == '?' )
{
return text;
}
return text + ".";
}
/// <summary>Split into lowercased words exactly like the official splitter.</summary>
public static List<string> SplitWords( string text, List<string> results = null )
{
results ??= new List<string>( 16 );
foreach ( Match m in Pattern.Matches( text ) )
{
results.Add( m.Value.ToLowerInvariant() );
}
return results;
}
}