Files
Mathieu LimonierandClaude Opus 5 6a6d745af4 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>
2026-08-22 22:36:16 +02:00

75 lines
3.6 KiB
C#

namespace MaBibli.Shared.Textes;
/// <summary>
/// Clé d'identification d'une <b>œuvre</b> à partir d'un titre, indépendamment de l'édition.
/// </summary>
/// <remarks>
/// C'est le rapprochement le moins mauvais dont on dispose, et il faut être clair sur ses limites.
/// <para>
/// <b>Pourquoi pas l'ISBN ?</b> Parce qu'il désigne une <i>édition</i>, pas une œuvre : le poche
/// de 1998, le grand format de 1991 et la réédition de 2015 portent trois ISBN différents pour le
/// même roman. Mesuré sur la bibliographie BnF de Bernard Werber : 183 notices de livres pour
/// 51 œuvres, chacune revenant en moyenne trois fois sous trois ISBN. Comparer les ISBN
/// répondrait donc « vous ne l'avez pas » à propos d'un livre posé sur l'étagère, ce qui est le
/// pire résultat possible pour une liste d'envies.
/// </para>
/// <para>
/// <b>Ce que la clé fait.</b> Elle coupe la mention de responsabilité ISBD (« <c> / </c> ») puis
/// le sous-titre (« <c> : </c> »), et met le reste à plat. C'est ce qui réunit
/// « Les thanatonautes », « Les thanatonautes : roman » et
/// « Les thanatonautes / Bernard Werber ».
/// </para>
/// <para>
/// <b>Ce qu'elle rate</b>, et qu'il ne faut pas prétendre autrement :
/// <list type="bullet">
/// <item>Deux œuvres distinctes partageant un titre principal sont confondues (« Œuvres »,
/// « Nouvelles »). Le risque est borné : la comparaison n'a lieu qu'<b>à auteur donné</b>.</item>
/// <item>Un tome n'est pas rapproché de son recueil : « Troisième humanité » et
/// « Troisième humanité. Tome 1 » restent deux lignes. Le point n'est pas coupé, faute de savoir
/// distinguer « Tome 1 » d'un titre qui contient légitimement un point.</item>
/// <item>Un titre retraduit ou changé au fil des rééditions n'est pas rapproché du tout.</item>
/// <item>Une œuvre possédée à l'intérieur d'une intégrale n'est pas repérée : l'intégrale a son
/// propre titre.</item>
/// </list>
/// Conséquence assumée : l'écran de bibliographie peut afficher comme « à découvrir » un livre
/// déjà présent sous un autre titre. Il ne fait donc que <b>griser</b> ce qu'il reconnaît, sans
/// jamais masquer une ligne — un faux négatif se voit et se corrige d'un coup d'œil, alors qu'une
/// ligne masquée à tort serait invisible.
/// </para>
/// </remarks>
public static class CleOeuvre
{
/// <summary>Sépare le titre de la mention de responsabilité : « Germinal / Émile Zola ».</summary>
private const string SeparateurResponsabilite = " / ";
/// <summary>Sépare le titre de son sous-titre : « Les thanatonautes : roman ».</summary>
private const string SeparateurSousTitre = " : ";
/// <summary>
/// Clé comparable d'un titre : « Les thanatonautes : roman » → <c>les thanatonautes</c>.
/// </summary>
public static string Cle(string? titre)
{
var s = titre?.Trim();
if (string.IsNullOrEmpty(s))
{
return string.Empty;
}
s = Tronquer(s, SeparateurResponsabilite);
var sansSousTitre = Tronquer(s, SeparateurSousTitre);
// Un titre qui n'est QUE son sous-titre (« : roman ») ne doit pas se réduire à rien :
// mieux vaut alors garder la chaîne entière que produire une clé vide, qui se
// confondrait avec toutes les autres clés vides.
var retenu = NormalisationTexte.Normaliser(sansSousTitre);
return retenu.Length > 0 ? retenu : NormalisationTexte.Normaliser(s);
}
private static string Tronquer(string s, string separateur)
{
var i = s.IndexOf(separateur, StringComparison.Ordinal);
return i >= 0 ? s[..i].Trim() : s;
}
}