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() integre. La seule facon d'obtenir « Emile » vers « Émile » sans charger tout le catalogue en memoire est de stocker la forme mise a plat, et d'y chercher un terme passe par exactement la meme fonction. NormalisationTexte fait cette mise a plat. La ponctuation y devient une espace au lieu de disparaitre : « P.F. Hamilton » doit donner trois mots, sans quoi le rapprochement par initiales serait impossible. RapprochementAuteurs porte la frontiere decidee dans CLAUDE.md. La cle de regroupement — mots normalises tries — couvre casse, accents et ordre inverse, et servira d'index unique. Les initiales abregees lui echappent : elles demandent d'aligner les mots un a un, avec retour arriere, parce qu'une correspondance gloutonne echouerait sur « P. Peter » face a « Peter Paul ». Le verdict est a trois niveaux, et l'asymetrie est deliberee : ne pas fusionner laisse deux fiches a reunir d'un clic, alors qu'une fusion erronee melange les livres de deux personnes et se defait mal. Un patronyme seul face a un nom complet ressort donc « suggere », jamais « certain ». Meme exigence sur l'alignement complet : « P. H. » et « Peter Hamilton » s'alignent par les seules initiales, ce qui ne concorde sur rien de verifiable — un mot entier commun est exige. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
2.8 KiB
C#
78 lines
2.8 KiB
C#
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace MaBibli.Shared.Textes;
|
|
|
|
/// <summary>
|
|
/// Mise à plat d'un texte pour la recherche et le regroupement : minuscules, sans accents,
|
|
/// sans ponctuation, espaces réduits.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <b>Pourquoi une forme stockée plutôt qu'une comparaison à la volée ?</b> SQLite ne sait pas
|
|
/// comparer sans tenir compte des accents : sa collation <c>NOCASE</c> ne couvre que l'ASCII, et
|
|
/// il n'existe pas de <c>unaccent()</c> intégré. Faire le travail en C# à l'écriture, dans une
|
|
/// colonne dédiée, est la seule façon d'obtenir « Emile » → « Émile » <b>sans</b> charger tout
|
|
/// le catalogue en mémoire à chaque recherche.
|
|
/// <para>
|
|
/// Conséquence à ne pas oublier : toute écriture doit recalculer la forme normalisée, et le
|
|
/// terme cherché doit passer par la <b>même</b> fonction que la valeur stockée.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class NormalisationTexte
|
|
{
|
|
/// <summary>
|
|
/// Forme comparable d'un texte quelconque : « Émile ZOLA » → « emile zola ».
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// La ponctuation devient une espace plutôt que de disparaître : « P.F. Hamilton » doit
|
|
/// donner trois mots (<c>p f hamilton</c>) et non un seul (<c>pf hamilton</c>), sans quoi
|
|
/// le rapprochement par initiales serait impossible.
|
|
/// </remarks>
|
|
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 (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);
|
|
}
|
|
|
|
/// <summary>Mots de la forme normalisée, sans mot vide.</summary>
|
|
public static string[] Mots(string? brut)
|
|
{
|
|
var normalise = Normaliser(brut);
|
|
return normalise.Length == 0
|
|
? []
|
|
: normalise.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
}
|
|
}
|