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. /// 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); } /// 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); } }