using System.Xml.Linq; using MaBibli.Shared.Dtos; namespace MaBibli.Api.Services.Isbn; /// /// Lecture d'une réponse SRU de la BnF au schéma dublincore. /// /// /// Fonction pure : aucun accès réseau, ce qui permet de la tester sur des réponses réelles /// enregistrées sur disque. /// public static class BnfSruParser { private static readonly XNamespace Srw = "http://www.loc.gov/zing/srw/"; private static readonly XNamespace Dc = "http://purl.org/dc/elements/1.1/"; /// Nombre de notices annoncé par <srw:numberOfRecords>, ou 0. public static int LireNombreDeNotices(string xml) { var doc = XDocument.Parse(xml); var valeur = doc.Descendants(Srw + "numberOfRecords").FirstOrDefault()?.Value; return int.TryParse(valeur, out var n) ? n : 0; } /// /// Convertit la réponse SRU en candidats, champs déjà nettoyés de la ponctuation ISBD. /// /// Corps de la réponse SRU. /// Forme d'ISBN qui a produit cette réponse (13 ou 10). /// URL de couverture OpenLibrary à attacher (la BnF n'en fournit pas). public static IReadOnlyList Parser(string xml, string isbnInterroge, string? urlCouverture) { var doc = XDocument.Parse(xml); var candidats = new List(); foreach (var record in doc.Descendants(Srw + "record")) { var data = record.Element(Srw + "recordData"); if (data is null) { continue; } // Plusieurs dc:creator sont possibles : un livre peut avoir plusieurs auteurs. var auteurs = Tous(data, "creator") .Select(NettoyageIsbd.Auteur) .Where(a => a is not null) .Select(a => a!) .Distinct() .ToList(); var titre = NettoyageIsbd.Titre(Premier(data, "title")); // Certaines notices recollent le nom de l'auteur en fin de titre sans le séparateur // ISBD — il faut donc les auteurs nettoyés avant de pouvoir finir le titre. titre = NettoyageIsbd.RetirerAuteurEnFin(titre, auteurs); if (titre is null) { // Une notice sans titre exploitable n'a aucun intérêt pour un pré-remplissage. continue; } candidats.Add(new CandidatLivre { Titre = titre, Auteurs = auteurs, Editeur = NettoyageIsbd.Editeur(Premier(data, "publisher")), Annee = Premier(data, "date"), Langue = Premier(data, "language"), CoverUrl = urlCouverture, Source = SourceMetadonnees.Bnf, IsbnInterroge = isbnInterroge, IdentifiantSource = record.Element(Srw + "recordIdentifier")?.Value?.Trim(), }); } return candidats; } private static string? Premier(XElement recordData, string nomLocal) => Tous(recordData, nomLocal).FirstOrDefault(); private static IEnumerable Tous(XElement recordData, string nomLocal) => recordData.Descendants(Dc + nomLocal) .Select(e => e.Value?.Trim()) .Where(v => !string.IsNullOrEmpty(v)) .Select(v => v!); }