diff --git a/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs b/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs index fa6deab..563b85e 100644 --- a/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs +++ b/MaBibli.Api/Services/Isbn/NettoyageIsbd.cs @@ -128,7 +128,9 @@ public static partial class NettoyageIsbd var pointRole = IndexDuPointDeRole(s); if (pointRole >= 0) { - s = s[..pointRole]; + // Quand le rôle suit une initiale (« Cormen, Thomas H. Auteur du texte »), le point + // qui sert de séparateur appartient aussi au nom : on le conserve. + s = EstPointDInitiale(s, pointRole) ? s[..(pointRole + 1)] : s[..pointRole]; } s = RetirerPonctuationFinale(s); @@ -174,12 +176,7 @@ public static partial class NettoyageIsbd 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) + if (EstPointDInitiale(s, s.Length - 1)) { break; } @@ -194,9 +191,15 @@ public static partial class NettoyageIsbd /// Position du point qui introduit le rôle, ou -1. /// /// - /// 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. + /// Un point suivi d'une espace sépare le rôle du nom. Le point d'une initiale + /// (« Cormen, Thomas H. ») n'en est pas un — le couper amputerait le prénom. + /// + /// Mais protéger l'initiale ne doit pas protéger le rôle qui la suit : « Cormen, Thomas H. + /// Auteur du texte » renvoyait auparavant « Thomas H. Auteur du texte Cormen ». Ce qui + /// tranche : dans « Nom, Prénom », la seule chose qui puisse suivre une initiale et faire + /// encore partie du nom est une autre initiale (« Tolkien, J. R. R. »). Tout le + /// reste est le rôle. + /// /// private static int IndexDuPointDeRole(string s) { @@ -207,16 +210,31 @@ public static partial class NettoyageIsbd continue; } - var estInitiale = i >= 1 - && char.IsUpper(s[i - 1]) - && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\''); - - if (!estInitiale) + if (EstPointDInitiale(s, i) && CommenceParUneInitiale(s, i + 2)) { - return i; + continue; } + + return i; } return -1; } + + /// + /// Le point en clôt-il une initiale (majuscule isolée) ? + /// + private static bool EstPointDInitiale(string s, int i) + => i >= 1 + && char.IsUpper(s[i - 1]) + && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\''); + + /// + /// Le texte à partir de commence-t-il par une initiale + /// (une majuscule suivie d'un point ou de la fin de chaîne) ? + /// + private static bool CommenceParUneInitiale(string s, int debut) + => debut < s.Length + && char.IsUpper(s[debut]) + && (debut + 1 == s.Length || s[debut + 1] == '.'); } diff --git a/MaBibli.Tests/NettoyageIsbdTests.cs b/MaBibli.Tests/NettoyageIsbdTests.cs index 493af12..7c11296 100644 --- a/MaBibli.Tests/NettoyageIsbdTests.cs +++ b/MaBibli.Tests/NettoyageIsbdTests.cs @@ -85,12 +85,17 @@ public class NettoyageIsbdTests public void Auteur_laisse_passer_ce_qui_est_deja_propre(string brut, string attendu) => Assert.Equal(attendu, NettoyageIsbd.Auteur(brut)); - [Fact] - public void Auteur_ne_coupe_pas_sur_une_initiale() - { - // « H. » est une initiale, pas un séparateur de rôle : la couper amputerait le prénom. - Assert.Equal("Thomas H. Cormen", NettoyageIsbd.Auteur("Cormen, Thomas H.")); - } + [Theory] + // Contre-exemples : le nom se termine légitimement par une initiale, il n'y a pas de rôle + // à retirer — couper au point amputerait le prénom. + [InlineData("Cormen, Thomas H.", "Thomas H. Cormen")] + [InlineData("Tolkien, J. R. R.", "J. R. R. Tolkien")] + // Cas réel : le rôle suit une initiale. Protéger le point de l'initiale ne doit pas + // protéger le rôle — sinon on obtient « Thomas H. Auteur du texte Cormen ». + [InlineData("Cormen, Thomas H. Auteur du texte", "Thomas H. Cormen")] + [InlineData("Tolkien, J. R. R. (1892-1973). Auteur du texte", "J. R. R. Tolkien")] + public void Auteur_distingue_une_initiale_d_un_role(string brut, string attendu) + => Assert.Equal(attendu, NettoyageIsbd.Auteur(brut)); [Theory] [InlineData("le Livre de poche (Paris)", "le Livre de poche")]