From 8808c195069e4dcf44218ebf7ca481a4c695fb12 Mon Sep 17 00:00:00 2001 From: Aldrik Ramaekers Date: Mon, 11 Mar 2024 21:47:49 +0100 Subject: export results to json,csv,xml format --- src/export.cpp | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/export.h | 6 ++ src/main.cpp | 17 ++++- src/search.cpp | 5 +- src/search.h | 1 + 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 src/export.cpp create mode 100644 src/export.h diff --git a/src/export.cpp b/src/export.cpp new file mode 100644 index 0000000..23e7bf6 --- /dev/null +++ b/src/export.cpp @@ -0,0 +1,207 @@ +#include "export.h" +#include "array.h" +#include "config.h" +#include + +static bool _str_has_extension(const utf8_int8_t *str, const utf8_int8_t *suffix) +{ + if (!str || !suffix) + return 0; + size_t lenstr = strlen(str); + size_t lensuffix = strlen(suffix); + if (lensuffix > lenstr) + return 0; + return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; +} + +static utf8_int8_t* _json_escape_str(utf8_int8_t* str, utf8_int8_t* buffer, size_t buffer_size) { + memset(buffer, 0, buffer_size); + + utf8_int8_t* buffer_orig = buffer; + utf8_int32_t ch; + while ((str = utf8codepoint(str, &ch)) && ch) + { + if (ch == '\\') { + utf8cat(buffer, "\\\\"); + buffer += 2; + } + else if (ch == '\"') { + utf8cat(buffer, "\\\""); + buffer += 2; + } + else { + buffer = utf8catcodepoint(buffer, ch, buffer_size - (buffer - buffer_orig) - 1); + } + } + return buffer_orig; +} + +static bool _ts_export_json(ts_search_result* result, const utf8_int8_t* path) { + FILE *write_file; + fopen_s(&write_file, path, "w"); + if (write_file == NULL) return false; + + const size_t escape_size = MAX_INPUT_LENGTH*2; // ballpark. + utf8_int8_t escape_buffer[escape_size]; + + fprintf(write_file, "{\n"); + + fprintf(write_file, "\"version\": 1,\n"); + fprintf(write_file, "\"path\": \"%s\",\n", _json_escape_str(result->directory_to_search, escape_buffer, escape_size)); + fprintf(write_file, "\"filter\": \"%s\",\n", _json_escape_str(result->file_filter, escape_buffer, escape_size)); + fprintf(write_file, "\"query\": \"%s\",\n", _json_escape_str(result->search_text, escape_buffer, escape_size)); + fprintf(write_file, "\"casesensitive\": %d,\n", result->respect_capitalization); + fprintf(write_file, "\"match_count\": %d,\n", result->match_count); + fprintf(write_file, "\"file_count\": %d,\n", result->file_count); + fprintf(write_file, "\"timestamp\": %llu,\n", result->timestamp); + + fprintf(write_file, "\"files\": [\n"); + + for (int i = 0; i < result->files.length; i++) { + ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i); + + fprintf(write_file, "{\n"); + fprintf(write_file, "\"path\": \"%s\",\n", _json_escape_str(file->path, escape_buffer, escape_size)); + + fprintf(write_file, "\"matches\": [\n"); + + bool first_match_of_file = true; + for (int i = 0; i < result->matches.length; i++) { + ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i); + if (match->file != file) continue; + + if (!first_match_of_file) fprintf(write_file, ",\n"); + first_match_of_file = false; + fprintf(write_file, "{\n"); + fprintf(write_file, "\"line_nr\": %d,\n", match->line_nr); + fprintf(write_file, "\"match_length\": %zu,\n", match->word_match_length); + fprintf(write_file, "\"match_offset\": %zu,\n", match->word_match_offset); + fprintf(write_file, "\"line\": \"%s\"\n", _json_escape_str(match->line_info, escape_buffer, escape_size)); + fprintf(write_file, "}\n"); + } + + fprintf(write_file, "]\n"); + if (i == result->files.length-1) fprintf(write_file, "}\n"); else fprintf(write_file, "},\n"); + } + + fprintf(write_file, "]\n"); + + fprintf(write_file, "}\n"); + + fclose(write_file); + return true; +} + +static bool _ts_export_csv(ts_search_result* result, const utf8_int8_t* path) { + FILE *write_file; + fopen_s(&write_file, path, "w"); + if (write_file == NULL) return false; + + fprintf(write_file, "VERSION,1\n"); + fprintf(write_file, "PATH,%s\n", result->directory_to_search); + fprintf(write_file, "FILTER,%s\n", result->file_filter); + fprintf(write_file, "QUERY,%s\n", result->search_text); + fprintf(write_file, "CASESENSITIVE,%d\n", result->respect_capitalization); + fprintf(write_file, "MATCH_COUNT,%d\n", result->match_count); + fprintf(write_file, "FILE_COUNT,%d\n", result->file_count); + fprintf(write_file, "TIMESTAMP,%llu\n", result->timestamp); + + for (int i = 0; i < result->files.length; i++) { + ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i); + fprintf(write_file, "FILE,%s\n", file->path); + + for (int i = 0; i < result->matches.length; i++) { + ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i); + if (match->file != file) continue; + fprintf(write_file, "MATCH,%d,%zu,%zu,%s\n", match->line_nr, match->word_match_length, match->word_match_offset, match->line_info); + } + } + + fclose(write_file); + return true; +} + +static utf8_int8_t* _xml_escape_str(utf8_int8_t* str, utf8_int8_t* buffer, size_t buffer_size) { + memset(buffer, 0, buffer_size); + + utf8_int8_t* buffer_orig = buffer; + utf8_int32_t ch; + while ((str = utf8codepoint(str, &ch)) && ch) + { + switch(ch) { + case '<': utf8cat(buffer, "<"); buffer += 4; break; + case '>': utf8cat(buffer, ">"); buffer += 4; break; + case '&': utf8cat(buffer, "&"); buffer += 5; break; + case '\'': utf8cat(buffer, "'"); buffer += 5; break; + case '"': utf8cat(buffer, """); buffer += 5; break; + default: buffer = utf8catcodepoint(buffer, ch, buffer_size - (buffer - buffer_orig) - 1); + } + } + return buffer_orig; +} + +static bool _ts_export_xml(ts_search_result* result, const utf8_int8_t* path) { + FILE *write_file; + fopen_s(&write_file, path, "w"); + if (write_file == NULL) return false; + + const size_t escape_size = MAX_INPUT_LENGTH*2; // ballpark. + utf8_int8_t escape_buffer[escape_size]; + + fprintf(write_file, "\n"); + + fprintf(write_file, "1\n"); + fprintf(write_file, "%s\n", _xml_escape_str(result->directory_to_search, escape_buffer, escape_size)); + fprintf(write_file, "%s\n", _xml_escape_str(result->file_filter, escape_buffer, escape_size)); + fprintf(write_file, "%s\n", _xml_escape_str(result->search_text, escape_buffer, escape_size)); + fprintf(write_file, "%d\n", result->respect_capitalization); + fprintf(write_file, "%d\n", result->match_count); + fprintf(write_file, "%d\n", result->file_count); + fprintf(write_file, "%llu\n", result->timestamp); + + for (int i = 0; i < result->files.length; i++) { + ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i); + + fprintf(write_file, "\n"); + fprintf(write_file, "%s\n", _xml_escape_str(file->path, escape_buffer, escape_size)); + + for (int i = 0; i < result->matches.length; i++) { + ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i); + if (match->file != file) continue; + + fprintf(write_file, "\n"); + fprintf(write_file, "%d\n", match->line_nr); + fprintf(write_file, "%zu\n", match->word_match_length); + fprintf(write_file, "%zu\n", match->word_match_offset); + fprintf(write_file, "%s\n", _xml_escape_str(match->line_info, escape_buffer, escape_size)); + fprintf(write_file, "\n"); + } + + fprintf(write_file, "\n"); + } + + fprintf(write_file, "\n"); + + fclose(write_file); + return true; +} + +bool ts_export_result(ts_search_result* result, const utf8_int8_t* path) { + if (result == NULL || path == NULL) return false; + if (!result->search_completed) return false; + result->is_saving = true; + + if (_str_has_extension(path, ".json")) { + return _ts_export_json(result, path); + } + if (_str_has_extension(path, ".csv")) { + return _ts_export_csv(result, path); + } + if (_str_has_extension(path, ".xml")) { + return _ts_export_xml(result, path); + } + + result->is_saving = false; + + return false; +} \ No newline at end of file diff --git a/src/export.h b/src/export.h new file mode 100644 index 0000000..1e7f824 --- /dev/null +++ b/src/export.h @@ -0,0 +1,6 @@ +#pragma once + +#include "search.h" +#include "platform.h" + +bool ts_export_result(ts_search_result* result, const utf8_int8_t* path); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 55487f2..bcbc4df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "platform.h" #include "image.h" #include "config.h" +#include "export.h" #include @@ -131,7 +132,7 @@ static void _ts_create_popups() { } } -static int _ts_create_menu() { +static int _ts_create_menu(int window_w, int window_h) { int menu_bar_h = 0; ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::Spectrum::Color(0xDDDDDD)); ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f); @@ -140,7 +141,9 @@ static int _ts_create_menu() { if (ImGui::BeginMenu("File")) { //ImGui::MenuItem("Open", "CTRL+O"); - //ImGui::MenuItem("Save", "CTRL+S"); + if (ImGui::MenuItem("Save As")) { + ifd::FileDialog::Instance().Save("FileSaveAsDialog", "Save results to file", "File (*.csv;*.json;*.xml){.csv,.json,.xml}"); + } ImGui::Separator(); if (ImGui::MenuItem("Exit")) { program_running = false; @@ -167,6 +170,14 @@ static int _ts_create_menu() { _ts_create_popups(); + if (ifd::FileDialog::Instance().IsDone("FileSaveAsDialog", window_w, window_h)) { + if (ifd::FileDialog::Instance().HasResult()) { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + ts_export_result(current_search_result, (const utf8_int8_t *)res.c_str()); + } + ifd::FileDialog::Instance().Close(); + } + return menu_bar_h; } @@ -356,7 +367,7 @@ void ts_create_gui(int window_w, int window_h) { ImGuiWindowFlags_MenuBar); ImGui::PopStyleVar(); - int menu_bar_h = _ts_create_menu(); + int menu_bar_h = _ts_create_menu(window_w, window_h); float pos_y = 0; pos_y += menu_bar_h + 15.0f; diff --git a/src/search.cpp b/src/search.cpp index 3897cc7..6762779 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -91,6 +91,7 @@ ts_search_result *ts_create_empty_search_result() new_result_buffer->memory = ts_memory_bucket_init(megabytes(10)); new_result_buffer->prev_result = current_search_result; new_result_buffer->timestamp = ts_platform_get_time(); + new_result_buffer->is_saving = false; new_result_buffer->files = ts_array_create(sizeof(ts_found_file)); new_result_buffer->files.reserve_jump = FILE_RESERVE_COUNT; @@ -103,6 +104,7 @@ ts_search_result *ts_create_empty_search_result() // filter buffers new_result_buffer->directory_to_search = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH); new_result_buffer->search_text = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH); + new_result_buffer->file_filter = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH); return new_result_buffer; } @@ -368,7 +370,7 @@ static void *_ts_list_files_thread(void *args) // Use this thread to cleanup previous result. if (info->prev_result) { - while (!info->prev_result->search_completed) { + while (!info->prev_result->search_completed && !info->prev_result->is_saving) { ts_thread_sleep(10); } ts_destroy_result(info->prev_result); @@ -407,6 +409,7 @@ void ts_start_search(utf8_int8_t *path, utf8_int8_t *filter, utf8_int8_t *query, ts_search_result *new_result = ts_create_empty_search_result(); snprintf(new_result->directory_to_search, MAX_INPUT_LENGTH, "%s", path); snprintf(new_result->search_text, MAX_INPUT_LENGTH, "%s", query); + snprintf(new_result->file_filter, MAX_INPUT_LENGTH, "%s", filter); new_result->filters = ts_get_filters(filter); new_result->max_ts_thread_count = thread_count; new_result->max_file_size = max_file_size; diff --git a/src/search.h b/src/search.h index da11e29..bdfdafa 100644 --- a/src/search.h +++ b/src/search.h @@ -32,6 +32,7 @@ typedef struct t_ts_search_result int file_list_read_cursor; bool cancel_search; bool search_completed; + bool is_saving; // search query utf8_int8_t *directory_to_search; -- cgit v1.2.3-70-g09d2