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_PERSON_ENTITY_SERVICE := tests/test_person_entity_service
|
||||
TEST_EML_ANALYZER := tests/test_eml_analyzer
|
||||
TEST_IBAN_ANALYZER := tests/test_iban_analyzer
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -647,6 +648,9 @@ $(TEST_PERSON_ENTITY_SERVICE): \
|
|||
$(TEST_EML_ANALYZER): tests/test_eml_analyzer.c src/core/eml_analyzer.c
|
||||
$(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): \
|
||||
tests/test_investigation_graph_load_task.c \
|
||||
src/core/investigation_graph_load_task.c \
|
||||
|
|
@ -726,7 +730,8 @@ test: \
|
|||
$(TEST_SOCIAL_ACCOUNT_SERVICE) \
|
||||
$(TEST_SOCIAL_PLATFORM) \
|
||||
$(TEST_PERSON_ENTITY_SERVICE) \
|
||||
$(TEST_EML_ANALYZER)
|
||||
$(TEST_EML_ANALYZER) \
|
||||
$(TEST_IBAN_ANALYZER)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -786,6 +791,7 @@ test: \
|
|||
@$(TEST_SOCIAL_PLATFORM)
|
||||
@$(TEST_PERSON_ENTITY_SERVICE)
|
||||
@$(TEST_EML_ANALYZER)
|
||||
@$(TEST_IBAN_ANALYZER)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.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. */
|
||||
typedef void (*MainWindowAnalyzeEmlCallback)(const char *evidence_identifier,
|
||||
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.
|
||||
|
|
@ -212,6 +215,9 @@ void main_window_set_edit_evidence_callback(
|
|||
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
||||
void main_window_set_analyze_eml_callback(MainWindow *main_window,
|
||||
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.
|
||||
|
|
|
|||
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.
|
||||
*
|
||||
* 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 entity_identifier UUID de l'entité.
|
||||
|
|
|
|||
|
|
@ -135,6 +135,9 @@ typedef void (*WorkspaceEditEvidenceCallback)(
|
|||
/** @brief Callback appelé pour analyser une preuve EML. */
|
||||
typedef void (*WorkspaceAnalyzeEmlCallback)(const char *evidence_identifier,
|
||||
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.
|
||||
|
|
@ -268,6 +271,9 @@ void workspace_set_edit_evidence_callback(
|
|||
/** @brief Définit le callback d'analyse locale d'une preuve EML. */
|
||||
void workspace_set_analyze_eml_callback(Workspace *workspace,
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@
|
|||
#include "views/create_person_dialog.h"
|
||||
#include "views/eml_analysis_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 <errno.h>
|
||||
|
|
@ -3386,6 +3389,144 @@ static void application_on_analyze_eml_requested(
|
|||
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.
|
||||
*/
|
||||
|
|
@ -6955,6 +7096,8 @@ static void application_on_activate(
|
|||
);
|
||||
main_window_set_analyze_eml_callback(application->main_window,
|
||||
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(
|
||||
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",
|
||||
NULL
|
||||
};
|
||||
static const char *const tool_catalog_tesseract_version_arguments[] =
|
||||
{
|
||||
"--version",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Catalogue statique initial.
|
||||
|
|
@ -109,6 +114,14 @@ static const ToolCatalogEntry tool_catalog_entries[] =
|
|||
.version_arguments =
|
||||
tool_catalog_openssl_version_arguments,
|
||||
.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;
|
||||
MainWindowAnalyzeEmlCallback analyze_eml_callback;
|
||||
gpointer analyze_eml_user_data;
|
||||
MainWindowAnalyzeRibCallback analyze_rib_callback;
|
||||
gpointer analyze_rib_user_data;
|
||||
|
||||
MainWindowGraphNodeMovedCallback
|
||||
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_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.
|
||||
|
|
@ -963,6 +973,8 @@ MainWindow *main_window_new(
|
|||
);
|
||||
workspace_set_analyze_eml_callback(main_window->workspace,
|
||||
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(
|
||||
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_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(
|
||||
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;
|
||||
int viewport_width = 0;
|
||||
int viewport_height = 0;
|
||||
|
||||
if (graph_view == NULL ||
|
||||
graph_view->node_layouts_by_identifier == NULL ||
|
||||
|
|
@ -4460,6 +4462,25 @@ gboolean investigation_graph_view_select_entity(
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ struct Workspace
|
|||
GtkWidget *verify_evidence_button;
|
||||
GtkWidget *edit_evidence_button;
|
||||
GtkWidget *analyze_eml_button;
|
||||
GtkWidget *analyze_rib_button;
|
||||
GtkWidget *evidence_preview_stack;
|
||||
GtkWidget *evidence_preview_status;
|
||||
GtkWidget *evidence_preview_picture;
|
||||
|
|
@ -112,6 +113,8 @@ struct Workspace
|
|||
gpointer edit_evidence_user_data;
|
||||
WorkspaceAnalyzeEmlCallback analyze_eml_callback;
|
||||
gpointer analyze_eml_user_data;
|
||||
WorkspaceAnalyzeRibCallback analyze_rib_callback;
|
||||
gpointer analyze_rib_user_data;
|
||||
|
||||
WorkspaceGraphNodeMovedCallback
|
||||
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_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)
|
||||
{
|
||||
|
|
@ -1449,6 +1461,13 @@ Workspace *workspace_new(void)
|
|||
g_signal_connect(workspace->analyze_eml_button, "clicked",
|
||||
G_CALLBACK(workspace_on_analyze_eml_clicked), workspace);
|
||||
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 =
|
||||
gtk_separator_new(
|
||||
|
|
@ -2433,6 +2452,8 @@ void workspace_set_selected_node(
|
|||
gtk_widget_set_sensitive(workspace->edit_evidence_button, FALSE);
|
||||
if (workspace->analyze_eml_button != NULL)
|
||||
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)
|
||||
{
|
||||
|
|
@ -2598,6 +2619,9 @@ void workspace_set_selected_evidence(
|
|||
char *lower = name != NULL ? g_ascii_strdown(name, -1) : NULL;
|
||||
gtk_widget_set_sensitive(workspace->analyze_eml_button,
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -3226,6 +3250,13 @@ void workspace_set_analyze_eml_callback(Workspace *workspace,
|
|||
workspace->analyze_eml_callback = callback;
|
||||
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(
|
||||
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",
|
||||
.executable_name = "openssl",
|
||||
.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(
|
||||
summary.total_count,
|
||||
==,
|
||||
5
|
||||
tool_catalog_get_count()
|
||||
);
|
||||
|
||||
g_assert_cmpuint(
|
||||
|
|
@ -1087,7 +1087,7 @@ static void test_tool_initializer_detect_versions(void)
|
|||
g_assert_cmpuint(
|
||||
summary.missing_count,
|
||||
==,
|
||||
3
|
||||
tool_catalog_get_count() - 2
|
||||
);
|
||||
|
||||
g_assert_cmpuint(
|
||||
|
|
@ -1248,7 +1248,7 @@ static void test_tool_initializer_version_failure_is_nonfatal(void)
|
|||
g_assert_cmpuint(
|
||||
summary.missing_count,
|
||||
==,
|
||||
3
|
||||
tool_catalog_get_count() - 2
|
||||
);
|
||||
|
||||
g_assert_cmpuint(
|
||||
|
|
|
|||
Loading…
Reference in a new issue