MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts, scan de code-barres, consultation hors-ligne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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.
|
||||
/// <para>
|
||||
/// Les <b>ligatures</b> <c>œ</c> et <c>æ</c> sont décomposées en <c>oe</c> et <c>ae</c>.
|
||||
/// ⚠️ 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, <i>L'Œuvre</i> de Zola — que la BnF
|
||||
/// écrit avec la ligature — resterait introuvable à qui tape « oeuvre » au clavier.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </para>
|
||||
/// </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 (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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Développement d'une ligature, ou <c>null</c> si le caractère n'en est pas une.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Volontairement limité à <c>œ</c> et <c>æ</c>, seules ligatures courantes du français.
|
||||
/// Les ligatures typographiques de présentation (<c>fi</c>, <c>fl</c>) ne sont pas traitées :
|
||||
/// elles n'apparaissent pas dans les catalogues, et rien n'a montré le besoin.
|
||||
/// </remarks>
|
||||
private static string? Ligature(char c) => c switch
|
||||
{
|
||||
'œ' or 'Œ' => "oe",
|
||||
'æ' or 'Æ' => "ae",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user