feat(ocr): extract and create IBAN entities from evidence
This commit is contained in:
parent
fb491639aa
commit
e6298e4f8a
18 changed files with 495 additions and 5 deletions
8
Makefile
8
Makefile
|
|
@ -125,6 +125,7 @@ TEST_SOCIAL_ACCOUNT_SERVICE := tests/test_social_account_service
|
||||||
TEST_SOCIAL_PLATFORM := tests/test_social_platform
|
TEST_SOCIAL_PLATFORM := tests/test_social_platform
|
||||||
TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service
|
TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service
|
||||||
TEST_EML_ANALYZER := tests/test_eml_analyzer
|
TEST_EML_ANALYZER := tests/test_eml_analyzer
|
||||||
|
TEST_IBAN_ANALYZER := tests/test_iban_analyzer
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -647,6 +648,9 @@ $(TEST_PERSON_ENTITY_SERVICE): \
|
||||||
$(TEST_EML_ANALYZER): tests/test_eml_analyzer.c src/core/eml_analyzer.c
|
$(TEST_EML_ANALYZER): tests/test_eml_analyzer.c src/core/eml_analyzer.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
$(TEST_IBAN_ANALYZER): tests/test_iban_analyzer.c src/core/iban_analyzer.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
||||||
tests/test_investigation_graph_load_task.c \
|
tests/test_investigation_graph_load_task.c \
|
||||||
src/core/investigation_graph_load_task.c \
|
src/core/investigation_graph_load_task.c \
|
||||||
|
|
@ -726,7 +730,8 @@ test: \
|
||||||
$(TEST_SOCIAL_ACCOUNT_SERVICE) \
|
$(TEST_SOCIAL_ACCOUNT_SERVICE) \
|
||||||
$(TEST_SOCIAL_PLATFORM) \
|
$(TEST_SOCIAL_PLATFORM) \
|
||||||
$(TEST_PERSON_ENTITY_SERVICE) \
|
$(TEST_PERSON_ENTITY_SERVICE) \
|
||||||
$(TEST_EML_ANALYZER)
|
$(TEST_EML_ANALYZER) \
|
||||||
|
$(TEST_IBAN_ANALYZER)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -786,6 +791,7 @@ test: \
|
||||||
@$(TEST_SOCIAL_PLATFORM)
|
@$(TEST_SOCIAL_PLATFORM)
|
||||||
@$(TEST_PERSON_ENTITY_SERVICE)
|
@$(TEST_PERSON_ENTITY_SERVICE)
|
||||||
@$(TEST_EML_ANALYZER)
|
@$(TEST_EML_ANALYZER)
|
||||||
|
@$(TEST_IBAN_ANALYZER)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
|
||||||
19
include/core/iban_analyzer.h
Normal file
19
include/core/iban_analyzer.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file iban_analyzer.h
|
||||||
|
* @brief Normalisation, validation et extraction locale d'IBAN.
|
||||||
|
******************************************************************************/
|
||||||
|
#ifndef LABFY_INVESTIGATION_IBAN_ANALYZER_H
|
||||||
|
#define LABFY_INVESTIGATION_IBAN_ANALYZER_H
|
||||||
|
#include <glib.h>
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
/** @brief Normalise un IBAN en majuscules sans séparateurs. */
|
||||||
|
char *iban_analyzer_normalize(const char *text);
|
||||||
|
/** @brief Vérifie la structure et la clé de contrôle modulo 97. */
|
||||||
|
gboolean iban_analyzer_validate(const char *iban);
|
||||||
|
/**
|
||||||
|
* @brief Extrait les IBAN valides d'un texte OCR.
|
||||||
|
* @return Tableau possédé de chaînes uniques normalisées.
|
||||||
|
*/
|
||||||
|
GPtrArray *iban_analyzer_extract(const char *ocr_text);
|
||||||
|
G_END_DECLS
|
||||||
|
#endif
|
||||||
13
include/core/rib_ocr.h
Normal file
13
include/core/rib_ocr.h
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file rib_ocr.h
|
||||||
|
* @brief Exécution locale de Tesseract pour les preuves de type RIB.
|
||||||
|
******************************************************************************/
|
||||||
|
#ifndef LABFY_INVESTIGATION_RIB_OCR_H
|
||||||
|
#define LABFY_INVESTIGATION_RIB_OCR_H
|
||||||
|
#include <glib.h>
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
/** @brief Exécute Tesseract localement et retourne le texte UTF-8 possédé. */
|
||||||
|
gboolean rib_ocr_extract_text(const char *image_path, char **out_text,
|
||||||
|
char **out_version, GError **error);
|
||||||
|
G_END_DECLS
|
||||||
|
#endif
|
||||||
|
|
@ -101,6 +101,9 @@ typedef void (*MainWindowEditEvidenceCallback)(
|
||||||
/** @brief Callback appelé pour analyser une preuve EML. */
|
/** @brief Callback appelé pour analyser une preuve EML. */
|
||||||
typedef void (*MainWindowAnalyzeEmlCallback)(const char *evidence_identifier,
|
typedef void (*MainWindowAnalyzeEmlCallback)(const char *evidence_identifier,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
/** @brief Callback appelé pour analyser un RIB par OCR. */
|
||||||
|
typedef void (*MainWindowAnalyzeRibCallback)(const char *evidence_identifier,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||||
|
|
@ -212,6 +215,9 @@ void main_window_set_edit_evidence_callback(
|
||||||
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
||||||
void main_window_set_analyze_eml_callback(MainWindow *main_window,
|
void main_window_set_analyze_eml_callback(MainWindow *main_window,
|
||||||
MainWindowAnalyzeEmlCallback callback, gpointer user_data);
|
MainWindowAnalyzeEmlCallback callback, gpointer user_data);
|
||||||
|
/** @brief Définit le callback d'analyse OCR d'un RIB. */
|
||||||
|
void main_window_set_analyze_rib_callback(MainWindow *main_window,
|
||||||
|
MainWindowAnalyzeRibCallback callback, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit le callback de fin de déplacement d'un nœud.
|
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||||
|
|
|
||||||
15
include/views/rib_ocr_review_dialog.h
Normal file
15
include/views/rib_ocr_review_dialog.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file rib_ocr_review_dialog.h
|
||||||
|
* @brief Révision humaine des résultats OCR d'un RIB.
|
||||||
|
******************************************************************************/
|
||||||
|
#ifndef LABFY_INVESTIGATION_RIB_OCR_REVIEW_DIALOG_H
|
||||||
|
#define LABFY_INVESTIGATION_RIB_OCR_REVIEW_DIALOG_H
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
typedef void (*RibOcrReviewCallback)(const char *iban, gpointer user_data);
|
||||||
|
/** @brief Présente le texte OCR et un IBAN révisable. */
|
||||||
|
void rib_ocr_review_dialog_present(GtkWindow *parent, const char *ocr_text,
|
||||||
|
const char *suggested_iban, RibOcrReviewCallback callback,
|
||||||
|
gpointer user_data);
|
||||||
|
G_END_DECLS
|
||||||
|
#endif
|
||||||
|
|
@ -186,7 +186,8 @@ const EntityRecord *investigation_graph_view_get_selected_entity(
|
||||||
/**
|
/**
|
||||||
* @brief Sélectionne une entité du graphe par son UUID.
|
* @brief Sélectionne une entité du graphe par son UUID.
|
||||||
*
|
*
|
||||||
* La vue reste inchangée si l'identifiant est absent du graphe.
|
* Le nœud trouvé est placé au centre de la zone visible. La vue reste
|
||||||
|
* inchangée si l'identifiant est absent du graphe.
|
||||||
*
|
*
|
||||||
* @param graph_view Vue graphique à modifier.
|
* @param graph_view Vue graphique à modifier.
|
||||||
* @param entity_identifier UUID de l'entité.
|
* @param entity_identifier UUID de l'entité.
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@ typedef void (*WorkspaceEditEvidenceCallback)(
|
||||||
/** @brief Callback appelé pour analyser une preuve EML. */
|
/** @brief Callback appelé pour analyser une preuve EML. */
|
||||||
typedef void (*WorkspaceAnalyzeEmlCallback)(const char *evidence_identifier,
|
typedef void (*WorkspaceAnalyzeEmlCallback)(const char *evidence_identifier,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
/** @brief Callback appelé pour analyser un RIB par OCR. */
|
||||||
|
typedef void (*WorkspaceAnalyzeRibCallback)(const char *evidence_identifier,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crée une nouvelle zone de travail.
|
* @brief Crée une nouvelle zone de travail.
|
||||||
|
|
@ -268,6 +271,9 @@ void workspace_set_edit_evidence_callback(
|
||||||
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
||||||
void workspace_set_analyze_eml_callback(Workspace *workspace,
|
void workspace_set_analyze_eml_callback(Workspace *workspace,
|
||||||
WorkspaceAnalyzeEmlCallback callback, gpointer user_data);
|
WorkspaceAnalyzeEmlCallback callback, gpointer user_data);
|
||||||
|
/** @brief Définit le callback d'analyse OCR d'un RIB. */
|
||||||
|
void workspace_set_analyze_rib_callback(Workspace *workspace,
|
||||||
|
WorkspaceAnalyzeRibCallback callback, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit le callback de vérification de la preuve affichée.
|
* @brief Définit le callback de vérification de la preuve affichée.
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@
|
||||||
#include "views/create_person_dialog.h"
|
#include "views/create_person_dialog.h"
|
||||||
#include "views/eml_analysis_dialog.h"
|
#include "views/eml_analysis_dialog.h"
|
||||||
#include "views/manage_entity_evidence_dialog.h"
|
#include "views/manage_entity_evidence_dialog.h"
|
||||||
|
#include "views/rib_ocr_review_dialog.h"
|
||||||
|
#include "core/rib_ocr.h"
|
||||||
|
#include "core/iban_analyzer.h"
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
@ -3386,6 +3389,144 @@ static void application_on_analyze_eml_requested(
|
||||||
evidence_dao_free(dao); g_clear_error(&error);
|
evidence_dao_free(dao); g_clear_error(&error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Contexte possédé pendant la révision OCR d'un RIB. */
|
||||||
|
typedef struct { Application *application; char *evidence_identifier;
|
||||||
|
char *ocr_text; char *tool_version; } ApplicationRibOcrContext;
|
||||||
|
/** @brief Libère le contexte OCR d'un RIB. */
|
||||||
|
static void application_rib_ocr_context_free(ApplicationRibOcrContext *context)
|
||||||
|
{
|
||||||
|
if (context == NULL) return;
|
||||||
|
g_free(context->evidence_identifier); g_free(context->ocr_text);
|
||||||
|
g_free(context->tool_version); g_free(context);
|
||||||
|
}
|
||||||
|
/** @brief Crée ou réutilise l'entité IBAN confirmée et la lie à la preuve. */
|
||||||
|
static void application_on_rib_ocr_confirmed(const char *iban,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ApplicationRibOcrContext *context = user_data;
|
||||||
|
Application *application = context != NULL ? context->application : NULL;
|
||||||
|
Database *database = NULL; EntityDao *entity_dao = NULL;
|
||||||
|
EvidenceEntityDao *link_dao = NULL; GPtrArray *entities = NULL;
|
||||||
|
EntityRecord *record = NULL; const char *entity_identifier = NULL;
|
||||||
|
char *new_identifier = NULL; char *timestamp = NULL; char *description = NULL;
|
||||||
|
GDateTime *now = NULL; GError *error = NULL; gboolean active = FALSE;
|
||||||
|
const InvestigationProject *project = NULL;
|
||||||
|
if (iban == NULL || application == NULL || application->session == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
database = investigation_session_get_database(application->session);
|
||||||
|
entity_dao = entity_dao_new(database, &error);
|
||||||
|
if (entity_dao == NULL) goto failure;
|
||||||
|
entities = entity_dao_list_all(entity_dao, &error);
|
||||||
|
if (entities == NULL) goto failure;
|
||||||
|
for (guint index = 0; index < entities->len; index++)
|
||||||
|
{
|
||||||
|
EntityRecord *candidate = g_ptr_array_index(entities, index);
|
||||||
|
if (g_strcmp0(entity_record_get_type_identifier(candidate), "iban") == 0 &&
|
||||||
|
g_strcmp0(entity_record_get_value(candidate), iban) == 0)
|
||||||
|
entity_identifier = entity_record_get_identifier(candidate);
|
||||||
|
}
|
||||||
|
if (!database_transaction_begin(database)) goto failure;
|
||||||
|
active = TRUE;
|
||||||
|
if (entity_identifier == NULL)
|
||||||
|
{
|
||||||
|
new_identifier = g_uuid_string_random(); now = g_date_time_new_now_utc();
|
||||||
|
timestamp = now != NULL ? g_date_time_format(now,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ") : NULL;
|
||||||
|
description = g_strdup_printf("IBAN extrait localement par OCR (%s).",
|
||||||
|
context->tool_version != NULL ? context->tool_version : "Tesseract");
|
||||||
|
record = entity_record_new(new_identifier, "iban", iban, "IBAN extrait du RIB",
|
||||||
|
description, 70, timestamp, timestamp, ENTITY_STATUS_ACTIVE, &error);
|
||||||
|
if (record == NULL || !entity_dao_insert(entity_dao, record, &error))
|
||||||
|
goto failure;
|
||||||
|
entity_identifier = new_identifier;
|
||||||
|
}
|
||||||
|
link_dao = evidence_entity_dao_new(database, &error);
|
||||||
|
if (link_dao == NULL) goto failure;
|
||||||
|
{
|
||||||
|
gboolean exists = FALSE;
|
||||||
|
if (!evidence_entity_dao_exists(link_dao, context->evidence_identifier,
|
||||||
|
entity_identifier, &exists, &error) ||
|
||||||
|
(!exists && !evidence_entity_dao_link(link_dao,
|
||||||
|
context->evidence_identifier, entity_identifier, &error)))
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
if (!database_transaction_commit(database)) goto failure;
|
||||||
|
active = FALSE; project = investigation_session_get_project(application->session);
|
||||||
|
g_free(application->pending_entity_selection_identifier);
|
||||||
|
application->pending_entity_selection_identifier =
|
||||||
|
g_strdup(entity_identifier);
|
||||||
|
main_window_set_status(application->main_window,
|
||||||
|
"Entité IBAN créée et reliée à la preuve.");
|
||||||
|
application_start_graph_loading(application,
|
||||||
|
investigation_project_get_database_path(project)); goto cleanup;
|
||||||
|
failure:
|
||||||
|
if (active) database_transaction_rollback(database);
|
||||||
|
application_present_error(application, "Intégration du RIB impossible",
|
||||||
|
error != NULL ? error->message : "La transaction a échoué.");
|
||||||
|
cleanup:
|
||||||
|
g_clear_error(&error); entity_record_free(record);
|
||||||
|
g_clear_pointer(&entities, g_ptr_array_unref); entity_dao_free(entity_dao);
|
||||||
|
evidence_entity_dao_free(link_dao); g_free(new_identifier);
|
||||||
|
g_free(timestamp); g_free(description); g_clear_pointer(&now, g_date_time_unref);
|
||||||
|
application_rib_ocr_context_free(context);
|
||||||
|
}
|
||||||
|
/** @brief Exécute localement Tesseract sur la preuve image sélectionnée. */
|
||||||
|
static void application_on_analyze_rib_requested(
|
||||||
|
const char *evidence_identifier, gpointer user_data)
|
||||||
|
{
|
||||||
|
Application *application = user_data; const InvestigationProject *project = NULL;
|
||||||
|
EvidenceDao *dao = NULL; EvidenceRecord *record = NULL;
|
||||||
|
ApplicationRibOcrContext *context = NULL; GPtrArray *ibans = NULL;
|
||||||
|
char *candidate_path = NULL; char *canonical_root = NULL; char *path = NULL;
|
||||||
|
char *output_path = NULL; GError *error = NULL;
|
||||||
|
char *work_copy_path = NULL; GFile *source_file = NULL; GFile *copy_file = NULL;
|
||||||
|
if (application == NULL || application->session == NULL) return;
|
||||||
|
dao = evidence_dao_new(investigation_session_get_database(application->session), &error);
|
||||||
|
if (dao != NULL) record = evidence_dao_find_by_identifier(dao,
|
||||||
|
evidence_identifier, &error);
|
||||||
|
project = investigation_session_get_project(application->session);
|
||||||
|
if (record == NULL || project == NULL) goto failure;
|
||||||
|
canonical_root = g_canonicalize_filename(
|
||||||
|
investigation_project_get_root_path(project), NULL);
|
||||||
|
candidate_path = g_build_filename(canonical_root,
|
||||||
|
evidence_record_get_relative_path(record), NULL);
|
||||||
|
path = g_canonicalize_filename(candidate_path, NULL);
|
||||||
|
if (path == NULL || !g_str_has_prefix(path, canonical_root) ||
|
||||||
|
(path[strlen(canonical_root)] != G_DIR_SEPARATOR &&
|
||||||
|
path[strlen(canonical_root)] != '\0')) goto failure;
|
||||||
|
work_copy_path = g_strdup_printf("%s/02_Preuves_Traitees/OCR/%s-%s",
|
||||||
|
canonical_root, evidence_identifier,
|
||||||
|
evidence_record_get_original_name(record));
|
||||||
|
source_file = g_file_new_for_path(path);
|
||||||
|
copy_file = g_file_new_for_path(work_copy_path);
|
||||||
|
if (!g_file_copy(source_file, copy_file, G_FILE_COPY_OVERWRITE,
|
||||||
|
NULL, NULL, NULL, &error)) goto failure;
|
||||||
|
context = g_new0(ApplicationRibOcrContext, 1);
|
||||||
|
context->application = application;
|
||||||
|
context->evidence_identifier = g_strdup(evidence_identifier);
|
||||||
|
if (!rib_ocr_extract_text(work_copy_path, &context->ocr_text,
|
||||||
|
&context->tool_version, &error)) goto failure;
|
||||||
|
output_path = g_strdup_printf("%s/02_Preuves_Traitees/OCR/%s-ocr.txt",
|
||||||
|
canonical_root, evidence_identifier);
|
||||||
|
if (!g_file_set_contents(output_path, context->ocr_text, -1, &error))
|
||||||
|
goto failure;
|
||||||
|
ibans = iban_analyzer_extract(context->ocr_text);
|
||||||
|
rib_ocr_review_dialog_present(main_window_get_window(application->main_window),
|
||||||
|
context->ocr_text, ibans->len > 0 ? g_ptr_array_index(ibans, 0) : NULL,
|
||||||
|
application_on_rib_ocr_confirmed, context);
|
||||||
|
context = NULL; goto cleanup;
|
||||||
|
failure:
|
||||||
|
application_rib_ocr_context_free(context); context = NULL;
|
||||||
|
application_present_error(application, "Analyse OCR impossible",
|
||||||
|
error != NULL ? error->message :
|
||||||
|
"La preuve n'a pas pu être analysée par Tesseract.");
|
||||||
|
cleanup:
|
||||||
|
g_clear_error(&error); g_clear_pointer(&ibans, g_ptr_array_unref);
|
||||||
|
g_free(output_path); g_free(path); g_free(candidate_path); g_free(canonical_root);
|
||||||
|
g_free(work_copy_path); g_clear_object(&source_file); g_clear_object(©_file);
|
||||||
|
evidence_record_free(record); evidence_dao_free(dao);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Prépare et démarre l’import asynchrone d’un fichier.
|
* @brief Prépare et démarre l’import asynchrone d’un fichier.
|
||||||
*/
|
*/
|
||||||
|
|
@ -6955,6 +7096,8 @@ static void application_on_activate(
|
||||||
);
|
);
|
||||||
main_window_set_analyze_eml_callback(application->main_window,
|
main_window_set_analyze_eml_callback(application->main_window,
|
||||||
application_on_analyze_eml_requested, application);
|
application_on_analyze_eml_requested, application);
|
||||||
|
main_window_set_analyze_rib_callback(application->main_window,
|
||||||
|
application_on_analyze_rib_requested, application);
|
||||||
|
|
||||||
main_window_set_graph_node_moved_callback(
|
main_window_set_graph_node_moved_callback(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
|
|
|
||||||
64
src/core/iban_analyzer.c
Normal file
64
src/core/iban_analyzer.c
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file iban_analyzer.c
|
||||||
|
* @brief Normalisation, validation et extraction locale d'IBAN.
|
||||||
|
******************************************************************************/
|
||||||
|
#include "core/iban_analyzer.h"
|
||||||
|
char *iban_analyzer_normalize(const char *text)
|
||||||
|
{
|
||||||
|
GString *result = NULL;
|
||||||
|
if (text == NULL) return NULL;
|
||||||
|
result = g_string_new(NULL);
|
||||||
|
for (const char *cursor = text; *cursor != '\0'; cursor++)
|
||||||
|
if (g_ascii_isalnum(*cursor))
|
||||||
|
g_string_append_c(result, g_ascii_toupper(*cursor));
|
||||||
|
if (result->len == 0) { g_string_free(result, TRUE); return NULL; }
|
||||||
|
return g_string_free(result, FALSE);
|
||||||
|
}
|
||||||
|
gboolean iban_analyzer_validate(const char *iban)
|
||||||
|
{
|
||||||
|
char *normalized = iban_analyzer_normalize(iban);
|
||||||
|
guint remainder = 0; gsize length = 0;
|
||||||
|
if (normalized == NULL) return FALSE;
|
||||||
|
length = strlen(normalized);
|
||||||
|
if (length < 15 || length > 34 || !g_ascii_isalpha(normalized[0]) ||
|
||||||
|
!g_ascii_isalpha(normalized[1]) || !g_ascii_isdigit(normalized[2]) ||
|
||||||
|
!g_ascii_isdigit(normalized[3])) { g_free(normalized); return FALSE; }
|
||||||
|
for (gsize offset = 0; offset < length; offset++)
|
||||||
|
{
|
||||||
|
char character = normalized[(offset + 4) % length];
|
||||||
|
if (g_ascii_isdigit(character))
|
||||||
|
remainder = (remainder * 10 + (guint)(character - '0')) % 97;
|
||||||
|
else if (g_ascii_isalpha(character))
|
||||||
|
{
|
||||||
|
guint value = (guint)(character - 'A') + 10;
|
||||||
|
remainder = (remainder * 10 + value / 10) % 97;
|
||||||
|
remainder = (remainder * 10 + value % 10) % 97;
|
||||||
|
}
|
||||||
|
else { g_free(normalized); return FALSE; }
|
||||||
|
}
|
||||||
|
g_free(normalized); return remainder == 1;
|
||||||
|
}
|
||||||
|
GPtrArray *iban_analyzer_extract(const char *text)
|
||||||
|
{
|
||||||
|
GPtrArray *results = g_ptr_array_new_with_free_func(g_free);
|
||||||
|
GRegex *regex = NULL; GMatchInfo *matches = NULL; GError *error = NULL;
|
||||||
|
if (text == NULL) return results;
|
||||||
|
regex = g_regex_new("(?i)\\b(?:FR[ \\t-]*[0-9]{2}(?:[ \\t-]*[A-Z0-9]){23}|[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30})\\b",
|
||||||
|
G_REGEX_OPTIMIZE, 0, &error);
|
||||||
|
if (regex == NULL) { g_clear_error(&error); return results; }
|
||||||
|
g_regex_match(regex, text, 0, &matches);
|
||||||
|
while (g_match_info_matches(matches))
|
||||||
|
{
|
||||||
|
char *raw = g_match_info_fetch(matches, 0);
|
||||||
|
char *candidate = iban_analyzer_normalize(raw);
|
||||||
|
gboolean duplicate = FALSE;
|
||||||
|
for (guint index = 0; candidate != NULL && index < results->len; index++)
|
||||||
|
if (g_strcmp0(candidate, g_ptr_array_index(results, index)) == 0)
|
||||||
|
duplicate = TRUE;
|
||||||
|
if (candidate != NULL && iban_analyzer_validate(candidate) && !duplicate)
|
||||||
|
g_ptr_array_add(results, candidate);
|
||||||
|
else g_free(candidate);
|
||||||
|
g_free(raw); g_match_info_next(matches, NULL);
|
||||||
|
}
|
||||||
|
g_match_info_free(matches); g_regex_unref(regex); return results;
|
||||||
|
}
|
||||||
45
src/core/rib_ocr.c
Normal file
45
src/core/rib_ocr.c
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file rib_ocr.c
|
||||||
|
* @brief Exécution locale de Tesseract pour les preuves de type RIB.
|
||||||
|
******************************************************************************/
|
||||||
|
#include "core/rib_ocr.h"
|
||||||
|
#include <gio/gio.h>
|
||||||
|
gboolean rib_ocr_extract_text(const char *image_path, char **out_text,
|
||||||
|
char **out_version, GError **error)
|
||||||
|
{
|
||||||
|
GSubprocess *process = NULL; GSubprocess *version_process = NULL;
|
||||||
|
char *stdout_text = NULL; char *stderr_text = NULL; char *version = NULL;
|
||||||
|
gboolean success = FALSE;
|
||||||
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||||
|
if (out_text != NULL) *out_text = NULL;
|
||||||
|
if (out_version != NULL) *out_version = NULL;
|
||||||
|
if (image_path == NULL || !g_file_test(image_path, G_FILE_TEST_IS_REGULAR))
|
||||||
|
{
|
||||||
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La preuve image à analyser est invalide."); return FALSE;
|
||||||
|
}
|
||||||
|
process = g_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
||||||
|
G_SUBPROCESS_FLAGS_STDERR_PIPE, error, "tesseract", image_path,
|
||||||
|
"stdout", "-l", "fra+eng", NULL);
|
||||||
|
if (process == NULL) goto cleanup;
|
||||||
|
if (!g_subprocess_communicate_utf8(process, NULL, NULL, &stdout_text,
|
||||||
|
&stderr_text, error) || !g_subprocess_get_successful(process))
|
||||||
|
{
|
||||||
|
if (error != NULL && *error == NULL)
|
||||||
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Tesseract a échoué : %s", stderr_text != NULL
|
||||||
|
? stderr_text : "erreur inconnue");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
version_process = g_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
||||||
|
G_SUBPROCESS_FLAGS_STDERR_PIPE, NULL, "tesseract", "--version", NULL);
|
||||||
|
if (version_process != NULL) g_subprocess_communicate_utf8(version_process,
|
||||||
|
NULL, NULL, &version, NULL, NULL);
|
||||||
|
if (out_text != NULL) *out_text = g_utf8_make_valid(stdout_text, -1);
|
||||||
|
if (out_version != NULL && version != NULL)
|
||||||
|
*out_version = g_strdup(g_strstrip(version));
|
||||||
|
success = out_text == NULL || *out_text != NULL;
|
||||||
|
cleanup:
|
||||||
|
g_free(stdout_text); g_free(stderr_text); g_free(version);
|
||||||
|
g_clear_object(&process); g_clear_object(&version_process); return success;
|
||||||
|
}
|
||||||
|
|
@ -59,6 +59,11 @@ static const char *const tool_catalog_openssl_version_arguments[] =
|
||||||
"version",
|
"version",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
static const char *const tool_catalog_tesseract_version_arguments[] =
|
||||||
|
{
|
||||||
|
"--version",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Catalogue statique initial.
|
* @brief Catalogue statique initial.
|
||||||
|
|
@ -109,6 +114,14 @@ static const ToolCatalogEntry tool_catalog_entries[] =
|
||||||
.version_arguments =
|
.version_arguments =
|
||||||
tool_catalog_openssl_version_arguments,
|
tool_catalog_openssl_version_arguments,
|
||||||
.version_argument_count = 1
|
.version_argument_count = 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.identifier = "ocr.tesseract",
|
||||||
|
.display_name = "Tesseract OCR",
|
||||||
|
.executable_name = "tesseract",
|
||||||
|
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||||
|
.version_arguments = tool_catalog_tesseract_version_arguments,
|
||||||
|
.version_argument_count = 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ struct MainWindow
|
||||||
gpointer edit_evidence_user_data;
|
gpointer edit_evidence_user_data;
|
||||||
MainWindowAnalyzeEmlCallback analyze_eml_callback;
|
MainWindowAnalyzeEmlCallback analyze_eml_callback;
|
||||||
gpointer analyze_eml_user_data;
|
gpointer analyze_eml_user_data;
|
||||||
|
MainWindowAnalyzeRibCallback analyze_rib_callback;
|
||||||
|
gpointer analyze_rib_user_data;
|
||||||
|
|
||||||
MainWindowGraphNodeMovedCallback
|
MainWindowGraphNodeMovedCallback
|
||||||
graph_node_moved_callback;
|
graph_node_moved_callback;
|
||||||
|
|
@ -202,6 +204,14 @@ static void main_window_on_analyze_eml_requested(const char *identifier,
|
||||||
main_window->analyze_eml_callback(identifier,
|
main_window->analyze_eml_callback(identifier,
|
||||||
main_window->analyze_eml_user_data);
|
main_window->analyze_eml_user_data);
|
||||||
}
|
}
|
||||||
|
/** @brief Relaie la demande d'analyse OCR d'un RIB. */
|
||||||
|
static void main_window_on_analyze_rib_requested(const char *identifier,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
MainWindow *window = data;
|
||||||
|
if (window != NULL && window->analyze_rib_callback != NULL)
|
||||||
|
window->analyze_rib_callback(identifier, window->analyze_rib_user_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Ouvre dans le workspace l'entité choisie dans la sidebar.
|
* @brief Ouvre dans le workspace l'entité choisie dans la sidebar.
|
||||||
|
|
@ -963,6 +973,8 @@ MainWindow *main_window_new(
|
||||||
);
|
);
|
||||||
workspace_set_analyze_eml_callback(main_window->workspace,
|
workspace_set_analyze_eml_callback(main_window->workspace,
|
||||||
main_window_on_analyze_eml_requested, main_window);
|
main_window_on_analyze_eml_requested, main_window);
|
||||||
|
workspace_set_analyze_rib_callback(main_window->workspace,
|
||||||
|
main_window_on_analyze_rib_requested, main_window);
|
||||||
|
|
||||||
workspace_widget = workspace_get_widget(
|
workspace_widget = workspace_get_widget(
|
||||||
main_window->workspace
|
main_window->workspace
|
||||||
|
|
@ -1511,6 +1523,13 @@ void main_window_set_analyze_eml_callback(MainWindow *main_window,
|
||||||
main_window->analyze_eml_callback = callback;
|
main_window->analyze_eml_callback = callback;
|
||||||
main_window->analyze_eml_user_data = user_data;
|
main_window->analyze_eml_user_data = user_data;
|
||||||
}
|
}
|
||||||
|
void main_window_set_analyze_rib_callback(MainWindow *main_window,
|
||||||
|
MainWindowAnalyzeRibCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (main_window == NULL) return;
|
||||||
|
main_window->analyze_rib_callback = callback;
|
||||||
|
main_window->analyze_rib_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void main_window_set_tree_selection_callback(
|
void main_window_set_tree_selection_callback(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
|
|
|
||||||
59
src/views/rib_ocr_review_dialog.c
Normal file
59
src/views/rib_ocr_review_dialog.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file rib_ocr_review_dialog.c
|
||||||
|
* @brief Révision humaine des résultats OCR d'un RIB.
|
||||||
|
******************************************************************************/
|
||||||
|
#include "views/rib_ocr_review_dialog.h"
|
||||||
|
#include "core/iban_analyzer.h"
|
||||||
|
typedef struct { GtkWindow *window; GtkEntry *iban; GtkLabel *error;
|
||||||
|
RibOcrReviewCallback callback; gpointer data; gboolean completed; } State;
|
||||||
|
/** @brief Libère l'état du dialogue. */
|
||||||
|
static void state_free(gpointer data) { g_free(data); }
|
||||||
|
/** @brief Valide et transmet l'IBAN corrigé. */
|
||||||
|
static void on_confirm(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
State *state = data; const char *text = gtk_editable_get_text(
|
||||||
|
GTK_EDITABLE(state->iban)); char *normalized = NULL; (void) button;
|
||||||
|
normalized = iban_analyzer_normalize(text);
|
||||||
|
if (!iban_analyzer_validate(normalized))
|
||||||
|
{ gtk_label_set_text(state->error, "IBAN invalide : vérifiez les caractères OCR."); g_free(normalized); return; }
|
||||||
|
state->completed = TRUE; state->callback(normalized, state->data);
|
||||||
|
g_free(normalized); gtk_window_close(state->window);
|
||||||
|
}
|
||||||
|
/** @brief Signale l'annulation au contrôleur. */
|
||||||
|
static gboolean on_close(GtkWindow *window, gpointer data)
|
||||||
|
{
|
||||||
|
State *state = data; (void) window;
|
||||||
|
if (!state->completed) state->callback(NULL, state->data);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
void rib_ocr_review_dialog_present(GtkWindow *parent, const char *ocr_text,
|
||||||
|
const char *suggested, RibOcrReviewCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
State *state = g_new0(State, 1); GtkWidget *root = gtk_box_new(
|
||||||
|
GTK_ORIENTATION_VERTICAL, 10); GtkWidget *scroll = gtk_scrolled_window_new();
|
||||||
|
GtkWidget *view = gtk_text_view_new(); GtkWidget *confirm = NULL;
|
||||||
|
state->window = GTK_WINDOW(gtk_window_new()); state->callback = callback;
|
||||||
|
state->data = user_data; state->iban = GTK_ENTRY(gtk_entry_new());
|
||||||
|
state->error = GTK_LABEL(gtk_label_new(NULL));
|
||||||
|
gtk_window_set_title(state->window, "Réviser l’analyse OCR du RIB");
|
||||||
|
gtk_window_set_transient_for(state->window, parent); gtk_window_set_modal(state->window, TRUE);
|
||||||
|
gtk_window_set_default_size(state->window, 760, 620);
|
||||||
|
gtk_widget_set_margin_top(root, 16); gtk_widget_set_margin_bottom(root, 16);
|
||||||
|
gtk_widget_set_margin_start(root, 16); gtk_widget_set_margin_end(root, 16);
|
||||||
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
|
||||||
|
gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)),
|
||||||
|
ocr_text != NULL ? ocr_text : "", -1);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), view);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_editable_set_text(GTK_EDITABLE(state->iban), suggested != NULL ? suggested : "");
|
||||||
|
confirm = gtk_button_new_with_label("Créer l’entité IBAN");
|
||||||
|
gtk_widget_add_css_class(confirm, "suggested-action");
|
||||||
|
gtk_box_append(GTK_BOX(root), gtk_label_new("Texte OCR brut"));
|
||||||
|
gtk_box_append(GTK_BOX(root), scroll); gtk_box_append(GTK_BOX(root), gtk_label_new("IBAN détecté ou corrigé"));
|
||||||
|
gtk_box_append(GTK_BOX(root), GTK_WIDGET(state->iban)); gtk_box_append(GTK_BOX(root), GTK_WIDGET(state->error));
|
||||||
|
gtk_box_append(GTK_BOX(root), confirm); gtk_window_set_child(state->window, root);
|
||||||
|
g_object_set_data_full(G_OBJECT(state->window), "state", state, state_free);
|
||||||
|
g_signal_connect(confirm, "clicked", G_CALLBACK(on_confirm), state);
|
||||||
|
g_signal_connect(state->window, "close-request", G_CALLBACK(on_close), state);
|
||||||
|
gtk_window_present(state->window);
|
||||||
|
}
|
||||||
|
|
@ -4435,6 +4435,8 @@ gboolean investigation_graph_view_select_entity(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
InvestigationGraphNodeLayout *node_layout = NULL;
|
InvestigationGraphNodeLayout *node_layout = NULL;
|
||||||
|
int viewport_width = 0;
|
||||||
|
int viewport_height = 0;
|
||||||
|
|
||||||
if (graph_view == NULL ||
|
if (graph_view == NULL ||
|
||||||
graph_view->node_layouts_by_identifier == NULL ||
|
graph_view->node_layouts_by_identifier == NULL ||
|
||||||
|
|
@ -4460,6 +4462,25 @@ gboolean investigation_graph_view_select_entity(
|
||||||
node_layout
|
node_layout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (graph_view->drawing_area != NULL)
|
||||||
|
{
|
||||||
|
viewport_width = gtk_widget_get_width(graph_view->drawing_area);
|
||||||
|
viewport_height = gtk_widget_get_height(graph_view->drawing_area);
|
||||||
|
|
||||||
|
if (viewport_width > 0 && viewport_height > 0)
|
||||||
|
{
|
||||||
|
graph_view->offset_x =
|
||||||
|
((double) viewport_width / 2.0) -
|
||||||
|
(investigation_graph_view_get_node_center_x(node_layout) *
|
||||||
|
graph_view->zoom);
|
||||||
|
graph_view->offset_y =
|
||||||
|
((double) viewport_height / 2.0) -
|
||||||
|
(investigation_graph_view_get_node_center_y(node_layout) *
|
||||||
|
graph_view->zoom);
|
||||||
|
gtk_widget_queue_draw(graph_view->drawing_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ struct Workspace
|
||||||
GtkWidget *verify_evidence_button;
|
GtkWidget *verify_evidence_button;
|
||||||
GtkWidget *edit_evidence_button;
|
GtkWidget *edit_evidence_button;
|
||||||
GtkWidget *analyze_eml_button;
|
GtkWidget *analyze_eml_button;
|
||||||
|
GtkWidget *analyze_rib_button;
|
||||||
GtkWidget *evidence_preview_stack;
|
GtkWidget *evidence_preview_stack;
|
||||||
GtkWidget *evidence_preview_status;
|
GtkWidget *evidence_preview_status;
|
||||||
GtkWidget *evidence_preview_picture;
|
GtkWidget *evidence_preview_picture;
|
||||||
|
|
@ -112,6 +113,8 @@ struct Workspace
|
||||||
gpointer edit_evidence_user_data;
|
gpointer edit_evidence_user_data;
|
||||||
WorkspaceAnalyzeEmlCallback analyze_eml_callback;
|
WorkspaceAnalyzeEmlCallback analyze_eml_callback;
|
||||||
gpointer analyze_eml_user_data;
|
gpointer analyze_eml_user_data;
|
||||||
|
WorkspaceAnalyzeRibCallback analyze_rib_callback;
|
||||||
|
gpointer analyze_rib_user_data;
|
||||||
|
|
||||||
WorkspaceGraphNodeMovedCallback
|
WorkspaceGraphNodeMovedCallback
|
||||||
graph_node_moved_callback;
|
graph_node_moved_callback;
|
||||||
|
|
@ -1079,6 +1082,15 @@ static void workspace_on_analyze_eml_clicked(GtkButton *button, gpointer data)
|
||||||
workspace->analyze_eml_callback(workspace->selected_evidence_identifier,
|
workspace->analyze_eml_callback(workspace->selected_evidence_identifier,
|
||||||
workspace->analyze_eml_user_data);
|
workspace->analyze_eml_user_data);
|
||||||
}
|
}
|
||||||
|
/** @brief Transmet la demande d'analyse OCR du RIB affiché. */
|
||||||
|
static void workspace_on_analyze_rib_clicked(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
Workspace *workspace = data; (void) button;
|
||||||
|
if (workspace != NULL && workspace->analyze_rib_callback != NULL &&
|
||||||
|
workspace->selected_evidence_identifier != NULL)
|
||||||
|
workspace->analyze_rib_callback(workspace->selected_evidence_identifier,
|
||||||
|
workspace->analyze_rib_user_data);
|
||||||
|
}
|
||||||
|
|
||||||
Workspace *workspace_new(void)
|
Workspace *workspace_new(void)
|
||||||
{
|
{
|
||||||
|
|
@ -1449,6 +1461,13 @@ Workspace *workspace_new(void)
|
||||||
g_signal_connect(workspace->analyze_eml_button, "clicked",
|
g_signal_connect(workspace->analyze_eml_button, "clicked",
|
||||||
G_CALLBACK(workspace_on_analyze_eml_clicked), workspace);
|
G_CALLBACK(workspace_on_analyze_eml_clicked), workspace);
|
||||||
gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_eml_button);
|
gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_eml_button);
|
||||||
|
workspace->analyze_rib_button = gtk_button_new_with_label(
|
||||||
|
"Analyser le RIB par OCR");
|
||||||
|
gtk_widget_set_halign(workspace->analyze_rib_button, GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE);
|
||||||
|
g_signal_connect(workspace->analyze_rib_button, "clicked",
|
||||||
|
G_CALLBACK(workspace_on_analyze_rib_clicked), workspace);
|
||||||
|
gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_rib_button);
|
||||||
|
|
||||||
evidence_separator =
|
evidence_separator =
|
||||||
gtk_separator_new(
|
gtk_separator_new(
|
||||||
|
|
@ -2433,6 +2452,8 @@ void workspace_set_selected_node(
|
||||||
gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE);
|
gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE);
|
||||||
if (workspace->analyze_eml_button != NULL)
|
if (workspace->analyze_eml_button != NULL)
|
||||||
gtk_widget_set_sensitive(workspace->analyze_eml_button, FALSE);
|
gtk_widget_set_sensitive(workspace->analyze_eml_button, FALSE);
|
||||||
|
if (workspace->analyze_rib_button != NULL)
|
||||||
|
gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE);
|
||||||
|
|
||||||
if (node == NULL)
|
if (node == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -2598,6 +2619,9 @@ void workspace_set_selected_evidence(
|
||||||
char *lower = name != NULL ? g_ascii_strdown(name, -1) : NULL;
|
char *lower = name != NULL ? g_ascii_strdown(name, -1) : NULL;
|
||||||
gtk_widget_set_sensitive(workspace->analyze_eml_button,
|
gtk_widget_set_sensitive(workspace->analyze_eml_button,
|
||||||
lower != NULL && g_str_has_suffix(lower, ".eml"));
|
lower != NULL && g_str_has_suffix(lower, ".eml"));
|
||||||
|
gtk_widget_set_sensitive(workspace->analyze_rib_button,
|
||||||
|
lower != NULL && (g_str_has_suffix(lower, ".jpg") ||
|
||||||
|
g_str_has_suffix(lower, ".jpeg") || g_str_has_suffix(lower, ".png")));
|
||||||
g_free(lower);
|
g_free(lower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3226,6 +3250,13 @@ void workspace_set_analyze_eml_callback(Workspace *workspace,
|
||||||
workspace->analyze_eml_callback = callback;
|
workspace->analyze_eml_callback = callback;
|
||||||
workspace->analyze_eml_user_data = user_data;
|
workspace->analyze_eml_user_data = user_data;
|
||||||
}
|
}
|
||||||
|
void workspace_set_analyze_rib_callback(Workspace *workspace,
|
||||||
|
WorkspaceAnalyzeRibCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (workspace == NULL) return;
|
||||||
|
workspace->analyze_rib_callback = callback;
|
||||||
|
workspace->analyze_rib_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void workspace_set_graph_node_moved_callback(
|
void workspace_set_graph_node_moved_callback(
|
||||||
Workspace *workspace,
|
Workspace *workspace,
|
||||||
|
|
|
||||||
23
tests/test_iban_analyzer.c
Normal file
23
tests/test_iban_analyzer.c
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_iban_analyzer.c
|
||||||
|
* @brief Tests unitaires de l'analyseur IBAN.
|
||||||
|
******************************************************************************/
|
||||||
|
#include "core/iban_analyzer.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
char *normalized = iban_analyzer_normalize(
|
||||||
|
"fr76 3000 6000 0112 3456 7890 189");
|
||||||
|
GPtrArray *results = NULL;
|
||||||
|
assert(strcmp(normalized, "FR7630006000011234567890189") == 0);
|
||||||
|
assert(iban_analyzer_validate(normalized));
|
||||||
|
assert(!iban_analyzer_validate("FR7630006000011234567890188"));
|
||||||
|
results = iban_analyzer_extract(
|
||||||
|
"IBAN : FR76 3000 6000 0112 3456 7890 189\nAutre texte");
|
||||||
|
assert(results != NULL && results->len == 1);
|
||||||
|
assert(strcmp(g_ptr_array_index(results, 0), normalized) == 0);
|
||||||
|
g_ptr_array_unref(results); g_free(normalized);
|
||||||
|
puts("IbanAnalyzer : tous les tests sont valides."); return 0;
|
||||||
|
}
|
||||||
|
|
@ -56,6 +56,12 @@ static const ExpectedToolCatalogEntry expected_catalog_entries[] =
|
||||||
.display_name = "OpenSSL",
|
.display_name = "OpenSSL",
|
||||||
.executable_name = "openssl",
|
.executable_name = "openssl",
|
||||||
.version_argument = "version"
|
.version_argument = "version"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.identifier = "ocr.tesseract",
|
||||||
|
.display_name = "Tesseract OCR",
|
||||||
|
.executable_name = "tesseract",
|
||||||
|
.version_argument = "--version"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1075,7 +1075,7 @@ static void test_tool_initializer_detect_versions(void)
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
summary.total_count,
|
summary.total_count,
|
||||||
==,
|
==,
|
||||||
5
|
tool_catalog_get_count()
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
|
|
@ -1087,7 +1087,7 @@ static void test_tool_initializer_detect_versions(void)
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
summary.missing_count,
|
summary.missing_count,
|
||||||
==,
|
==,
|
||||||
3
|
tool_catalog_get_count() - 2
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
|
|
@ -1248,7 +1248,7 @@ static void test_tool_initializer_version_failure_is_nonfatal(void)
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
summary.missing_count,
|
summary.missing_count,
|
||||||
==,
|
==,
|
||||||
3
|
tool_catalog_get_count() - 2
|
||||||
);
|
);
|
||||||
|
|
||||||
g_assert_cmpuint(
|
g_assert_cmpuint(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue