Files
Anonyme/app/utils/highlightEntities.tsx
2025-08-09 15:23:20 +02:00

201 lines
5.0 KiB
TypeScript

import { ReactNode } from "react";
export const patterns = [
{
regex: /<PERSON>/g,
className: "bg-blue-200 text-blue-800",
label: "Personne",
},
{
regex: /<EMAIL_ADDRESS>/g,
className: "bg-green-200 text-green-800",
label: "Adresse Email",
},
{
regex: /<PHONE_NUMBER>/g,
className: "bg-purple-200 text-purple-800",
label: "N° de Téléphone",
},
{
regex: /<LOCATION>/g,
className: "bg-red-200 text-red-800",
label: "Lieu",
},
{
regex: /<IBAN>/g,
className: "bg-yellow-200 text-yellow-800",
label: "IBAN",
},
{
regex: /<ORGANIZATION>/g,
className: "bg-indigo-200 text-indigo-800",
label: "Organisation",
},
{
regex: /<FLEXIBLE_DATE>/g,
className: "bg-pink-200 text-pink-800",
label: "Date",
},
{
regex: /<BE_ADDRESS>/g,
className: "bg-cyan-200 text-cyan-800",
label: "Adresse (BE)",
},
{
regex: /<BE_PHONE_NUMBER>/g,
className: "bg-violet-200 text-violet-800",
label: "N° de Tél. (BE)",
},
{
regex: /<CREDIT_CARD>/g,
className: "bg-orange-200 text-orange-800",
label: "Carte de Crédit",
},
{
regex: /<URL>/g,
className: "bg-teal-200 text-teal-800",
label: "URL",
},
{
regex: /<IP_ADDRESS>/g,
className: "bg-gray-300 text-gray-900",
label: "Adresse IP",
},
{
regex: /<DATE_TIME>/g,
className: "bg-pink-300 text-pink-900",
label: "Date & Heure",
},
{
regex: /<NRP>/g,
className: "bg-red-300 text-red-900",
label: "N° Registre National",
},
{
regex: /<BE_VAT>/g,
className: "bg-yellow-300 text-yellow-900",
label: "TVA (BE)",
},
{
regex: /<BE_ENTERPRISE_NUMBER>/g,
className: "bg-lime-200 text-lime-800",
label: "N° d'entreprise (BE)",
},
{
regex: /<BE_PRO_ID>/g,
className: "bg-emerald-200 text-emerald-800",
label: "ID Pro (BE)",
},
];
interface EntityMapping {
originalValue: string;
anonymizedValue: string;
entityType: string;
startIndex: number;
endIndex: number;
}
export const highlightEntities = (
text: string,
entityMappings?: EntityMapping[]
): ReactNode => {
if (!text) return text;
const replacements: Array<{
start: number;
end: number;
element: ReactNode;
}> = [];
// Trouver toutes les correspondances
patterns.forEach((pattern, patternIndex) => {
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
let match;
let matchCount = 0; // Compteur pour ce type d'entité
while ((match = regex.exec(text)) !== null) {
const start = match.index;
const end = match.index + match[0].length;
// Vérifier qu'il n'y a pas de chevauchement avec des remplacements existants
const hasOverlap = replacements.some(
(r) =>
(start >= r.start && start < r.end) || (end > r.start && end <= r.end)
);
if (!hasOverlap) {
matchCount++; // Incrémenter le compteur pour ce type
let displayLabel = pattern.label;
const displayClass = pattern.className;
if (entityMappings) {
// Chercher le mapping correspondant à cette position et ce type
const matchingMapping = entityMappings.find(
(mapping) => mapping.entityType === pattern.label
);
if (matchingMapping) {
// Utiliser directement la valeur anonymisée du mapping
// qui correspond à cette occurrence (basée sur l'ordre d'apparition)
const entityType = pattern.label;
const mappingsOfThisType = entityMappings.filter(
(m) => m.entityType === entityType
);
// Prendre le mapping correspondant à cette occurrence
if (mappingsOfThisType[matchCount - 1]) {
displayLabel = mappingsOfThisType[matchCount - 1].anonymizedValue;
} else {
// Fallback si pas de mapping trouvé
displayLabel = `${entityType} [${matchCount}]`;
}
}
}
const element = (
<span
key={`${patternIndex}-${start}`}
className={`${displayClass} px-2 py-1 rounded-md font-medium text-xs inline-block mx-0.5 shadow-sm border`}
title={`${displayLabel} anonymisé`}
>
{displayLabel}
</span>
);
replacements.push({ start, end, element });
}
}
});
// Trier les remplacements par position
replacements.sort((a, b) => a.start - b.start);
// Construire le résultat final
if (replacements.length === 0) {
return text;
}
const parts: ReactNode[] = [];
let lastIndex = 0;
replacements.forEach((replacement) => {
// Ajouter le texte avant le remplacement
if (replacement.start > lastIndex) {
parts.push(text.slice(lastIndex, replacement.start));
}
// Ajouter l'élément de remplacement
parts.push(replacement.element);
lastIndex = replacement.end;
});
// Ajouter le texte restant
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts;
};