Retirer le nom de l'auteur colle en fin de titre BnF

Cas mesure, ISBN 9782749942636 :

  dc:title   = "Le plus grand defi ... (Nouvelle ed. augmentee) Aurélien Barrau"
  dc:creator = "Barrau, Aurélien (1973-....). Auteur du texte"

Le nom est recolle en fin de titre sans le separateur ISBD « / », donc la
regle de nettoyage existante ne peut rien y faire : la notice est sale a
la source.

L'heuristique est volontairement stricte — comparaison en fin de chaine
uniquement, contre un auteur deja nettoye, et seulement si le nom
commence sur une frontiere de mot. « Lettre a Émile Zola » n'est pas
touche, et un titre qui EST le nom de l'auteur (biographie, recueil) est
laisse intact plutot que reduit a rien.

La comparaison passe par CompareOptions.IgnoreNonSpace plutot que par une
normalisation NFD : elle ignore les accents sans changer les longueurs,
donc les indices calcules restent valables sur la chaine d'origine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-18 02:22:24 +02:00
co-authored by Claude Opus 5
parent 493cf80615
commit 4a8bc98f50
2 changed files with 112 additions and 0 deletions
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace MaBibli.Api.Services.Isbn; namespace MaBibli.Api.Services.Isbn;
@@ -43,6 +44,71 @@ public static partial class NettoyageIsbd
return s.Length == 0 ? null : s; return s.Length == 0 ? null : s;
} }
/// <summary>
/// Retire du titre le nom d'auteur qui y est collé en fin de chaîne.
/// </summary>
/// <remarks>
/// Cas réel mesuré, ISBN <c>9782749942636</c> :
/// <c>dc:title</c> vaut « Le plus grand défi … (Nouvelle éd. augmentée) <b>Aurélien Barrau</b> »
/// alors que <c>dc:creator</c> vaut « Barrau, Aurélien (1973-....) ». Le nom est recollé
/// <b>sans</b> le séparateur ISBD « / », donc <see cref="Titre"/> ne peut rien y faire : la
/// notice est sale à la source.
/// <para>
/// L'heuristique est volontairement stricte — comparaison en <b>fin de chaîne</b> uniquement,
/// et seulement contre un auteur déjà nettoyé. Un titre qui contient légitimement un nom de
/// personne ailleurs (« Lettre à Émile Zola ») n'est pas touché, et un titre qui <i>est</i>
/// le nom de l'auteur (biographie, recueil) est laissé intact plutôt que réduit à rien.
/// </para>
/// </remarks>
public static string? RetirerAuteurEnFin(string? titre, IEnumerable<string?> auteurs)
{
var s = titre?.Trim();
if (string.IsNullOrEmpty(s))
{
return null;
}
foreach (var auteur in auteurs)
{
var nom = auteur?.Trim();
if (string.IsNullOrEmpty(nom) || nom.Length >= s.Length)
{
// Un nom aussi long que le titre ne laisserait rien : c'est une biographie
// ou un recueil, pas une notice sale.
continue;
}
var debut = s.Length - nom.Length;
// IgnoreNonSpace ignore les accents sans changer les longueurs, contrairement à une
// normalisation NFD : les indices calculés restent valables sur la chaîne d'origine.
var identique = string.Compare(
s, debut, nom, 0, nom.Length,
CultureInfo.InvariantCulture,
CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) == 0;
if (!identique)
{
continue;
}
// Le nom doit commencer sur une frontière de mot, sinon « …Barrau » couperait aussi
// dans un mot qui se termine par les mêmes lettres.
if (char.IsLetterOrDigit(s[debut - 1]))
{
continue;
}
var reste = s[..debut].TrimEnd(' ', ',', ';', '.', '-', '/', ':');
if (reste.Length > 0)
{
return reste;
}
}
return s;
}
/// <summary> /// <summary>
/// Auteur : retirer les dates entre parenthèses, le rôle qui suit le point, /// Auteur : retirer les dates entre parenthèses, le rôle qui suit le point,
/// puis inverser « Nom, Prénom » en « Prénom Nom ». /// puis inverser « Nom, Prénom » en « Prénom Nom ».
+46
View File
@@ -22,6 +22,52 @@ public class NettoyageIsbdTests
public void Titre_laisse_passer_ce_qui_est_deja_propre(string brut, string attendu) public void Titre_laisse_passer_ce_qui_est_deja_propre(string brut, string attendu)
=> Assert.Equal(attendu, NettoyageIsbd.Titre(brut)); => Assert.Equal(attendu, NettoyageIsbd.Titre(brut));
/// <summary>
/// Cas réel mesuré sur l'ISBN <c>9782749942636</c> : la BnF recolle le nom de l'auteur en
/// fin de titre <b>sans</b> le séparateur ISBD, ce que <see cref="NettoyageIsbd.Titre"/> ne
/// peut donc pas voir.
/// </summary>
[Fact]
public void Titre_retire_le_nom_de_lauteur_colle_en_fin()
{
const string brut = "Le plus grand défi de l'histoire de l'humanité : face à la "
+ "catastrophe écologique et sociale (Nouvelle éd. augmentée) Aurélien Barrau";
var titre = NettoyageIsbd.RetirerAuteurEnFin(brut, ["Aurélien Barrau"]);
Assert.Equal(
"Le plus grand défi de l'histoire de l'humanité : face à la catastrophe écologique "
+ "et sociale (Nouvelle éd. augmentée)",
titre);
}
[Theory]
// Contre-exemple central : le nom est bien là, mais pas en fin de chaîne. On n'y touche pas.
[InlineData("Lettre à Émile Zola", "Guy de Maupassant")]
[InlineData("Émile Zola et son temps", "Émile Zola")]
// Le titre EST le nom de l'auteur (biographie, recueil) : le retirer ne laisserait rien.
[InlineData("Aurélien Barrau", "Aurélien Barrau")]
// Fin de mot fortuite : « Zola » ne doit pas être découpé dans « Gorgonzola ».
[InlineData("Éloge du gorgonzola", "Zola")]
// Auteur inconnu du titre.
[InlineData("Germinal", "Émile Zola")]
public void Titre_ne_mutile_pas_un_nom_legitime(string titre, string auteur)
=> Assert.Equal(titre, NettoyageIsbd.RetirerAuteurEnFin(titre, [auteur]));
[Fact]
public void Titre_tolere_une_difference_daccent_ou_de_casse_sur_le_nom()
=> Assert.Equal(
"Le grand défi",
NettoyageIsbd.RetirerAuteurEnFin("Le grand défi AURELIEN BARRAU", ["Aurélien Barrau"]));
[Fact]
public void Titre_sans_auteur_connu_reste_intact()
{
Assert.Equal("Germinal", NettoyageIsbd.RetirerAuteurEnFin("Germinal", []));
Assert.Equal("Germinal", NettoyageIsbd.RetirerAuteurEnFin("Germinal", [null, " "]));
Assert.Null(NettoyageIsbd.RetirerAuteurEnFin(null, ["Émile Zola"]));
}
[Theory] [Theory]
[InlineData("Zola, Émile (1840-1902). Auteur du texte", "Émile Zola")] [InlineData("Zola, Émile (1840-1902). Auteur du texte", "Émile Zola")]
[InlineData("Saint-Exupéry, Antoine de (1900-1944). Auteur du texte", "Antoine de Saint-Exupéry")] [InlineData("Saint-Exupéry, Antoine de (1900-1944). Auteur du texte", "Antoine de Saint-Exupéry")]