using MaBibli.Shared.Dtos; namespace MaBibli.Api.Services.Isbn; /// /// Transformation d'une édition OpenLibrary en candidat. Fonctions pures, testables sans réseau. /// public static class OpenLibraryMapper { /// /// Clés d'auteurs portées par l'édition elle-même (/authors/OL…A), éventuellement vide. /// /// /// Ces clés ne sont pas des noms : chacune impose un appel à /// /authors/{id}.json. Oublier ce second appel donne un titre rempli et un auteur vide /// — symptôme exact observé chez BookLogr. /// public static IReadOnlyList ClesAuteurs(OpenLibraryEdition edition) => edition.Authors? .Select(a => a.Key) .Where(k => !string.IsNullOrWhiteSpace(k)) .Select(k => k!) .ToList() ?? []; /// Clés d'auteurs portées par l'œuvre, utilisées en repli. public static IReadOnlyList ClesAuteurs(OpenLibraryWork work) => work.Authors? .Select(a => a.Author?.Key) .Where(k => !string.IsNullOrWhiteSpace(k)) .Select(k => k!) .ToList() ?? []; /// Clé de l'œuvre rattachée (/works/OL…W), ou null. public static string? CleOeuvre(OpenLibraryEdition edition) => edition.Works?.Select(w => w.Key).FirstOrDefault(k => !string.IsNullOrWhiteSpace(k)); /// Nom affichable d'un auteur OpenLibrary. public static string? Nom(OpenLibraryAuthor auteur) { var nom = auteur.Name ?? auteur.PersonalName; return string.IsNullOrWhiteSpace(nom) ? null : nom.Trim(); } /// /// Assemble le candidat final. vient des appels /// /authors/{id}.json déjà effectués par le client. /// public static CandidatLivre? VersCandidat( OpenLibraryEdition edition, IReadOnlyList nomsAuteurs, string isbnInterroge, string? urlCouverture) { var titre = edition.Title?.Trim(); if (string.IsNullOrEmpty(titre)) { return null; } if (!string.IsNullOrWhiteSpace(edition.Subtitle)) { titre = $"{titre} : {edition.Subtitle.Trim()}"; } // Les noms résolus par /authors/{id}.json arrivent déjà séparés. En dernier repli la // mention de responsabilité n'est qu'une phrase : on la garde entière plutôt que de // parier sur ses virgules, qui séparent aussi bien deux auteurs qu'un « Zola, Émile ». var auteurs = nomsAuteurs.Count > 0 ? nomsAuteurs : Liste(NettoyerByStatement(edition.ByStatement)); return new CandidatLivre { Titre = titre, Auteurs = auteurs, Editeur = edition.Publishers?.FirstOrDefault()?.Trim().Trim('[', ']'), Annee = string.IsNullOrWhiteSpace(edition.PublishDate) ? null : edition.PublishDate.Trim(), Langue = edition.Languages? .Select(l => l.Key) .FirstOrDefault(k => !string.IsNullOrWhiteSpace(k))? .Replace("/languages/", string.Empty), CoverUrl = urlCouverture, Source = SourceMetadonnees.OpenLibrary, IsbnInterroge = isbnInterroge, IdentifiantSource = edition.Key, UrlNotice = UrlNotice(edition.Key), }; } private static string? UrlNotice(string? cle) => string.IsNullOrWhiteSpace(cle) ? null : $"https://openlibrary.org/{cle.TrimStart('/')}"; private static IReadOnlyList Liste(string? valeur) => string.IsNullOrWhiteSpace(valeur) ? [] : [valeur]; /// /// Dernier repli quand aucun auteur n'est résolvable : la mention de responsabilité brute, /// débarrassée de sa ponctuation finale (« Thomas H. Cormen ... [et al.]. »). /// private static string? NettoyerByStatement(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.TrimEnd('.', ' ', ','); return s.Length == 0 ? null : s; } }