Recuperation des metadonnees d'un livre a partir de son ISBN, cote serveur uniquement (aucune interface, aucune persistance). Cascade : BnF en ISBN-13, puis BnF en ISBN-10 converti, puis OpenLibrary. La conversion 13 -> 10 est indispensable et non optionnelle : la BnF indexe l'ISBN tel qu'imprime, les ouvrages d'avant 2007 ne portent qu'un ISBN-10 et sont introuvables par l'EAN-13 que lit le scanner. Toutes les notices trouvees sont remontees (jusqu'a 5) : un meme ISBN peut correspondre a plusieurs reeditions, le choix revient a l'utilisateur. Le double appel /isbn puis /authors d'OpenLibrary est bien fait, avec repli sur l'oeuvre quand l'edition ne porte aucun auteur : c'est le bug de BookLogr (titre rempli, auteur vide) qu'il ne faut pas reproduire. La couverture vient toujours d'OpenLibrary, avec ?default=false pour obtenir un 404 plutot qu'une image placeholder. 87 tests xUnit, sur fixtures enregistrees : aucune dependance au reseau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 lines
4.6 KiB
C#
157 lines
4.6 KiB
C#
using System.Text.RegularExpressions;
|
|
|
|
namespace MaBibli.Api.Services.Isbn;
|
|
|
|
/// <summary>
|
|
/// Nettoyage de la ponctuation ISBD des champs Dublin Core de la BnF.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Les champs BnF ne sont pas exploitables bruts. Règles validées le 2026-08-17 (CLAUDE.md) :
|
|
/// <list type="bullet">
|
|
/// <item><c>dc:title</c> : <c>Germinal / Émile Zola ; préface…</c> → <c>Germinal</c></item>
|
|
/// <item><c>dc:creator</c> : <c>Zola, Émile (1840-1902). Auteur du texte</c> → <c>Émile Zola</c></item>
|
|
/// <item><c>dc:publisher</c> : <c>le Livre de poche (Paris)</c> → <c>le Livre de poche</c></item>
|
|
/// </list>
|
|
/// Les valeurs déjà propres doivent traverser ces règles <b>inchangées</b>.
|
|
/// </remarks>
|
|
public static partial class NettoyageIsbd
|
|
{
|
|
[GeneratedRegex(@"\s*\([^)]*\)")]
|
|
private static partial Regex Parentheses();
|
|
|
|
[GeneratedRegex(@"\s*\([^)]*\)\s*$")]
|
|
private static partial Regex ParenthesesFinales();
|
|
|
|
/// <summary>
|
|
/// Titre : couper à la première mention de responsabilité (« / »).
|
|
/// </summary>
|
|
public static string? Titre(string? brut)
|
|
{
|
|
var s = brut?.Trim();
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var i = s.IndexOf(" / ", StringComparison.Ordinal);
|
|
if (i >= 0)
|
|
{
|
|
s = s[..i];
|
|
}
|
|
|
|
s = s.Trim();
|
|
return s.Length == 0 ? null : s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Auteur : retirer les dates entre parenthèses, le rôle qui suit le point,
|
|
/// puis inverser « Nom, Prénom » en « Prénom Nom ».
|
|
/// </summary>
|
|
public static string? Auteur(string? brut)
|
|
{
|
|
var s = brut?.Trim();
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// 1. Dates de vie et autres qualificatifs entre parenthèses : (1840-1902), (1932-....)
|
|
s = Parentheses().Replace(s, string.Empty).Trim();
|
|
|
|
// 2. Rôle après le point : « . Auteur du texte », « . Préfacier ».
|
|
var pointRole = IndexDuPointDeRole(s);
|
|
if (pointRole >= 0)
|
|
{
|
|
s = s[..pointRole];
|
|
}
|
|
|
|
s = RetirerPonctuationFinale(s);
|
|
|
|
// 3. Inversion « Nom, Prénom » → « Prénom Nom » (sur la première virgule seulement).
|
|
var virgule = s.IndexOf(", ", StringComparison.Ordinal);
|
|
if (virgule > 0)
|
|
{
|
|
var nom = s[..virgule].Trim();
|
|
var prenom = s[(virgule + 2)..].Trim();
|
|
if (nom.Length > 0 && prenom.Length > 0)
|
|
{
|
|
s = $"{prenom} {nom}";
|
|
}
|
|
}
|
|
|
|
s = s.Trim();
|
|
return s.Length == 0 ? null : s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Éditeur : retirer la ville entre parenthèses en fin de chaîne.
|
|
/// </summary>
|
|
public static string? Editeur(string? brut)
|
|
{
|
|
var s = brut?.Trim();
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
s = ParenthesesFinales().Replace(s, string.Empty).Trim();
|
|
return s.Length == 0 ? null : s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retire les virgules et espaces de fin, et le point final <b>sauf</b> s'il appartient
|
|
/// à une initiale (« Cormen, Thomas H. » doit garder son point).
|
|
/// </summary>
|
|
private static string RetirerPonctuationFinale(string s)
|
|
{
|
|
s = s.TrimEnd(' ', ',');
|
|
|
|
while (s.EndsWith('.'))
|
|
{
|
|
var i = s.Length - 1;
|
|
var estInitiale = i >= 1
|
|
&& char.IsUpper(s[i - 1])
|
|
&& (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\'');
|
|
|
|
if (estInitiale)
|
|
{
|
|
break;
|
|
}
|
|
|
|
s = s[..^1].TrimEnd(' ', ',');
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Position du point qui introduit le rôle, ou -1.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Un point suivi d'une espace sépare le rôle du nom. On ignore les points d'initiales
|
|
/// (« Cormen, Thomas H. Auteur ») : un point précédé d'une majuscule isolée n'est pas
|
|
/// un séparateur de rôle.
|
|
/// </remarks>
|
|
private static int IndexDuPointDeRole(string s)
|
|
{
|
|
for (var i = 0; i < s.Length - 1; i++)
|
|
{
|
|
if (s[i] != '.' || s[i + 1] != ' ')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var estInitiale = i >= 1
|
|
&& char.IsUpper(s[i - 1])
|
|
&& (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\'');
|
|
|
|
if (!estInitiale)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|