Pour répondre à la nécessité d'afficher les modfiications apportées par les utilisateurs entre différentes versions d'un texte, nous nous sommes tournés vers la bibliothèque open-source google-diff-match-patch. Cette bibliothèque, disponible pour plusieurs langages avec une API cohérente et des tests unitaires complets, s'est avérée parfaitement adaptée à notre cas d'utilisation.
Cet article détaille l'intégration et l'utilisation des versions Java et JavaScript de cette bibliothèque dans un projet démo basé sur JFinal.
Intégration des Dépendances
Pour intégrer la bibliothèque, ajoutez la dépendance appropriée à votre fichier de configuraton de build (par exemple, pom.xml pour Maven) :
Version Java
<!-- Dépendance Java pour google-diff-match-patch -->
<dependency>
<groupId>org.clojars.brenton</groupId>
<artifactId>google-diff-match-patch</artifactId>
<version>0.1</version>
</dependency>
Version JavaScript (via Webjars)
<!-- Dépendance JavaScript via Webjars -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>google-diff-match-patch</artifactId>
<version>895a9512bb</version>
</dependency>
Implémentation avec JFinal
La bibliothèque expose une classe principale, diff_match_patch, dont les méthodes clés pour notre besoin sont diff_main (pour calculer les différences) et diff_prettyHtml (pour formater le résultat en HTML).
Nous allons créer un contrôleur et un service pour exposer une API de comparaison et une page de visualisation des différences.
Étant donné que nous utilisons Webjars pour les ressources statiques JavaScript, un gestionnaire personnalisé est nécessaire pour servir ces ressources :
package com.example.jfinal.handler;
import com.jfinal.handler.Handler;
import com.jfinal.kit.LogKit;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Gestionnaire de ressources statiques Webjars.
*/
public class WebJarResourceHandler extends Handler {
@Override
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
if (target.startsWith("/webjars/")) {
// Ajoute le préfixe pour localiser la ressource dans le ClassLoader
String resourcePath = target.replaceFirst("/webjars/", "META-INF/resources/webjars/");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = this.getClass().getClassLoader().getResourceAsStream(resourcePath);
if (inputStream != null) {
outputStream = response.getOutputStream();
IOUtils.copy(inputStream, outputStream);
isHandled[0] = true; // Marque la requête comme traitée
} else {
// Si la ressource n'est pas trouvée, passe la requête au gestionnaire suivant
this.next.handle(target, request, response, isHandled);
}
} catch (IOException e) {
LogKit.error("Erreur lors de la lecture de la ressource WebJar : " + resourcePath, e);
// En cas d'erreur, passer la requête au suivant peut être une option, ou renvoyer une erreur 404
this.next.handle(target, request, response, isHandled);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
} else {
// Transmet la requête au gestionnaire suivant si elle ne concerne pas les Webjars
this.next.handle(target, request, response, isHandled);
}
}
}
N'oubliez pas d'enregistrer ce gestionnaire dans votre configuration JFinal (AppConfig) :
@Override
public void configHandler(Handlers me) {
me.add(new WebJarResourceHandler());
// Autres gestionnaires...
}
Exemple d'Utilisation
Test Console Java
Voici un exemple simple démontrant l'utilisation de la bibliothèque en Java pour comparer deux chaînes de caractères et afficher le résultat au format HTML.
package com.example.jfinal.util;
import com.google.diff_match_patch;
import com.google.diff_match_patch.Diff;
import java.util.LinkedList;
public class TextDifferenceCalculator {
public static void main(String[] args) {
diff_match_patch dmp = new diff_match_patch();
// Texte version précédente
String previousText = "Je suis un résident légal de la République Populaire de Chine, j'écris un texte de test aujourd'hui, sans autre intention.";
// Texte version actuelle
String currentText = "Je suis un résident légal chinois, j'ai l'intention d'écrire aujourd'hui une chaîne de caractères pour tester le contenu, sans autre but !";
// Calcul des différences
LinkedList<diff> diffs = dmp.diff_main(previousText, currentText);
// Affichage du résultat brut (liste de différences)
System.out.println("Résultat brut des différences : " + diffs);
// Affichage du résultat formaté en HTML
String prettyHtmlResult = dmp.diff_prettyHtml(diffs);
System.out.println("Résultat en HTML : " + prettyHtmlResult);
}
}
</diff>
Le résultat de ce test sera similaire à ceci :
Résultat brut des différences : [Diff(EQUAL, "Je suis un résident légal chinois,"), Diff(DELETE, "de la République Populaire"), Diff(EQUAL, "aujourd'hui j'écris un texte"), Diff(INSERT, "j'ai l'intention d'écrire"), Diff(EQUAL, "de test"), Diff(INSERT, "une chaîne de caractères pour tester le contenu"), Diff(EQUAL, "caractères,"), Diff(DELETE, "et"), Diff(INSERT, "et"), Diff(EQUAL, "n'ai pas"), Diff(DELETE, "d'autres"), Diff(INSERT, "autre"), Diff(EQUAL, "intentions."), Diff(DELETE, ""), Diff(INSERT, " !")]
Résultat en HTML : <SPAN TITLE="i=0">Je suis un résident légal chinois,</SPAN><DEL STYLE="background:#FFE6E6;" TITLE="i=29">de la République Populaire</DEL><SPAN TITLE="29">aujourd'hui j'écris un texte</SPAN><INS STYLE="background:#E6FFE6;" TITLE="12">j'ai l'intention d'écrire</INS><SPAN TITLE="41">de test</SPAN><INS STYLE="background:#E6FFE6;" TITLE="16">une chaîne de caractères pour tester le contenu</INS><SPAN TITLE="69">caractères,</SPAN><DEL STYLE="background:#FFE6E6;" TITLE="7">et</DEL><SPAN TITLE="71">n'ai pas</SPAN><DEL STYLE="background:#FFE6E6;" TITLE="7">d'autres</DEL><INS STYLE="background:#E6FFE6;" TITLE="5">autre</INS><SPAN TITLE="75">intentions.</SPAN><DEL STYLE="background:#FFE6E6;" TITLE="1"></DEL><INS STYLE="background:#E6FFE6;" TITLE="1"> !</INS>
Visualisation dans une Page Web
L'exemple suivant montre comment intégrer cette fonctionnalité dans une page HTML, en utilisant la version JavaScript de la bibliothèque pour une comparaison directe côté client.
<html lang="fr">
<head>
<meta charset="UTF-8"/>
<title>Comparaison de Contenus Textuels</title>
<style>
.diff-container { margin: 20px auto; width: 600px; font-family: sans-serif; }
.text-section { margin-bottom: 20px; border: 1px solid #ccc; padding: 10px; }
.text-section h4 { margin-top: 0; text-align: center; background-color: #f0f0f0; padding: 8px; }
.content-display { height: 150px; overflow-y: auto; border: 1px solid #e0e0e0; background-color: #fff; padding: 5px; }
.comparison-result { margin-top: 30px; border: 1px solid #90caf9; background-color: #e3f2fd; padding: 10px; }
.comparison-result h4 { margin-top: 0; text-align: center; background-color: #90caf9; color: white; padding: 8px; }
/* Styles pour les différences */
ins { background-color: #e6ffed; text-decoration: none; }
del { background-color: #ffe6e6; text-decoration: none; }
</style>
</head>
<body>
<div class="diff-container">
<div style="display: flex; justify-content: space-between;">
<div class="text-section" style="width: 48%;">
<h4>Texte Précédent</h4>
<div class="content-display" id="previous-text-content"></div>
</div>
<div class="text-section" style="width: 48%;">
<h4>Texte Actuel</h4>
<div class="content-display" id="current-text-content"></div>
</div>
</div>
<div class="comparison-result">
<h4>Comparaison des Différences</h4>
<div class="content-display" id="diff-result-display"></div>
</div>
</div>
<!-- Inclusion de jQuery (si nécessaire pour d'autres parties de votre application) -->
<script src="/assets/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<!-- Inclusion de la bibliothèque google-diff-match-patch via Webjar -->
<script src="/webjars/google-diff-match-patch/895a9512bb/diff_match_patch.js" type="text/javascript"></script>
<script>
// Textes à comparer
const oldText = "Je suis un résident légal de la République Populaire de Chine, j'écris un texte de test aujourd'hui, sans autre intention.";
const newText = "Je suis un résident légal chinois, j'ai l'intention d'écrire aujourd'hui une chaîne de caractères pour tester le contenu, sans autre but !";
// Affichage des textes originaux
document.getElementById('previous-text-content').innerText = oldText;
document.getElementById('current-text-content').innerText = newText;
// Initialisation de la bibliothèque Diff Match Patch
const dmp = new diff_match_patch();
// Calcul et affichage des différences en HTML
const differences = dmp.diff_main(oldText, newText);
const htmlDifferences = dmp.diff_prettyHtml(differences);
document.getElementById('diff-result-display').innerHTML = htmlDifferences;
</script>
</body>
</html>
L'exécution de ce code générera une page web affichant les deux textes côte à côte et une section contenant les différences mises en évidence avec des couleurs (vert pour les ajouts, rouge pour les suppressions).