using System.Globalization;
using System.Text;
namespace MaBibli.Shared.Textes;
///
/// Mise à plat d'un texte pour la recherche et le regroupement : minuscules, sans accents,
/// sans ponctuation, espaces réduits.
///
///
/// Pourquoi une forme stockée plutôt qu'une comparaison à la volée ? SQLite ne sait pas
/// comparer sans tenir compte des accents : sa collation NOCASE ne couvre que l'ASCII, et
/// il n'existe pas de unaccent() intégré. Faire le travail en C# à l'écriture, dans une
/// colonne dédiée, est la seule façon d'obtenir « Emile » → « Émile » sans charger tout
/// le catalogue en mémoire à chaque recherche.
///
/// Conséquence à ne pas oublier : toute écriture doit recalculer la forme normalisée, et le
/// terme cherché doit passer par la même fonction que la valeur stockée.
///
///
public static class NormalisationTexte
{
///
/// Forme comparable d'un texte quelconque : « Émile ZOLA » → « emile zola ».
///
///
/// La ponctuation devient une espace plutôt que de disparaître : « P.F. Hamilton » doit
/// donner trois mots (p f hamilton) et non un seul (pf hamilton), sans quoi
/// le rapprochement par initiales serait impossible.
///
/// Les ligatures œ et æ sont décomposées en oe et ae.
/// ⚠️ Ce n'est pas ce que fait la décomposition NFD, qui sépare les accents mais laisse les
/// ligatures intactes : sans ce traitement explicite, L'Œuvre de Zola — que la BnF
/// écrit avec la ligature — resterait introuvable à qui tape « oeuvre » au clavier.
///
///
/// Le choix s'est porté sur une table explicite plutôt que sur NFKD, qui aurait fait bien
/// davantage : « ² » → « 2 », « Ⅻ » → « XII », les espaces insécables, les formes de
/// présentation. Une normalisation de recherche doit rester prévisible ; on ne défait ici que
/// ce dont on a constaté le besoin.
///
///
public static string Normaliser(string? brut)
{
if (string.IsNullOrWhiteSpace(brut))
{
return string.Empty;
}
// NFD sépare la lettre de son accent ; on ne garde ensuite que la lettre.
var decompose = brut.Normalize(NormalizationForm.FormD);
var sortie = new StringBuilder(decompose.Length);
var espaceEnAttente = false;
foreach (var c in decompose)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
{
continue; // Accent détaché par la décomposition.
}
if (Ligature(c) is { } lettres)
{
if (espaceEnAttente && sortie.Length > 0)
{
sortie.Append(' ');
}
espaceEnAttente = false;
sortie.Append(lettres);
continue;
}
if (char.IsLetterOrDigit(c))
{
if (espaceEnAttente && sortie.Length > 0)
{
sortie.Append(' ');
}
espaceEnAttente = false;
sortie.Append(char.ToLowerInvariant(c));
continue;
}
// Tout le reste (espaces, points, virgules, tirets…) est un séparateur de mots.
espaceEnAttente = true;
}
return sortie.ToString().Normalize(NormalizationForm.FormC);
}
///
/// Développement d'une ligature, ou null si le caractère n'en est pas une.
///
///
/// Volontairement limité à œ et æ, seules ligatures courantes du français.
/// Les ligatures typographiques de présentation (fi, fl) ne sont pas traitées :
/// elles n'apparaissent pas dans les catalogues, et rien n'a montré le besoin.
///
private static string? Ligature(char c) => c switch
{
'œ' or 'Œ' => "oe",
'æ' or 'Æ' => "ae",
_ => null,
};
/// Mots de la forme normalisée, sans mot vide.
public static string[] Mots(string? brut)
{
var normalise = Normaliser(brut);
return normalise.Length == 0
? []
: normalise.Split(' ', StringSplitOptions.RemoveEmptyEntries);
}
}