aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Infobox/CurrencyConversion.c474
-rw-r--r--src/Infobox/CurrencyConversion.h9
-rw-r--r--src/Routes/Search.c28
-rw-r--r--src/Utility/JsonHelper.c139
-rw-r--r--src/Utility/JsonHelper.h17
5 files changed, 666 insertions, 1 deletions
diff --git a/src/Infobox/CurrencyConversion.c b/src/Infobox/CurrencyConversion.c
new file mode 100644
index 0000000..f9d1fa6
--- /dev/null
+++ b/src/Infobox/CurrencyConversion.c
@@ -0,0 +1,474 @@
+#include "CurrencyConversion.h"
+#include "../Cache/Cache.h"
+#include "../Utility/HttpClient.h"
+#include "../Utility/JsonHelper.h"
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ const char *code;
+ const char *name;
+ const char *symbol;
+ int is_crypto;
+} CurrencyDef;
+
+static const CurrencyDef CURRENCIES[] = {
+ {"USD", "US Dollar", "$", 0},
+ {"EUR", "Euro", "€", 0},
+ {"GBP", "British Pound", "£", 0},
+ {"JPY", "Japanese Yen", "¥", 0},
+ {"AUD", "Australian Dollar", "A$", 0},
+ {"CAD", "Canadian Dollar", "C$", 0},
+ {"CHF", "Swiss Franc", "CHF", 0},
+ {"CNY", "Chinese Yuan", "¥", 0},
+ {"INR", "Indian Rupee", "₹", 0},
+ {"KRW", "South Korean Won", "₩", 0},
+ {"BRL", "Brazilian Real", "R$", 0},
+ {"RUB", "Russian Ruble", "₽", 0},
+ {"ZAR", "South African Rand", "R", 0},
+ {"MXN", "Mexican Peso", "MX$", 0},
+ {"SGD", "Singapore Dollar", "S$", 0},
+ {"HKD", "Hong Kong Dollar", "HK$", 0},
+ {"NOK", "Norwegian Krone", "kr", 0},
+ {"SEK", "Swedish Krona", "kr", 0},
+ {"DKK", "Danish Krone", "kr", 0},
+ {"NZD", "New Zealand Dollar", "NZ$", 0},
+ {"TRY", "Turkish Lira", "₺", 0},
+ {"PLN", "Polish Zloty", "zł", 0},
+ {"THB", "Thai Baht", "฿", 0},
+ {"IDR", "Indonesian Rupiah", "Rp", 0},
+ {"HUF", "Hungarian Forint", "Ft", 0},
+ {"CZK", "Czech Koruna", "Kč", 0},
+ {"ILS", "Israeli Shekel", "₪", 0},
+ {"CLP", "Chilean Peso", "CLP$", 0},
+ {"PHP", "Philippine Peso", "₱", 0},
+ {"AED", "UAE Dirham", "د.إ", 0},
+ {"COP", "Colombian Peso", "COP$", 0},
+ {"SAR", "Saudi Riyal", "﷼", 0},
+ {"MYR", "Malaysian Ringgit", "RM", 0},
+ {"RON", "Romanian Leu", "lei", 0},
+ {"ARS", "Argentine Peso", "ARS$", 0},
+ {"PKR", "Pakistani Rupee", "₨", 0},
+ {"EGP", "Egyptian Pound", "£", 0},
+ {"VND", "Vietnamese Dong", "₫", 0},
+ {"NGN", "Nigerian Naira", "₦", 0},
+ {"BDT", "Bangladeshi Taka", "৳", 0},
+ {"UAH", "Ukrainian Hryvnia", "₴", 0},
+ {"PEN", "Peruvian Sol", "S/", 0},
+ {"BGN", "Bulgarian Lev", "лв", 0},
+ {"HRK", "Croatian Kuna", "kn", 0},
+ {"ISK", "Icelandic Krona", "kr", 0},
+ {"MAD", "Moroccan Dirham", "د.م.", 0},
+ {"KES", "Kenyan Shilling", "KSh", 0},
+ {"QAR", "Qatari Riyal", "﷼", 0},
+ {"KWD", "Kuwaiti Dinar", "د.ك", 0},
+ {"BHD", "Bahraini Dinar", ".د.ب", 0},
+ {"OMR", "Omani Rial", "﷼", 0},
+ {"JOD", "Jordanian Dinar", "د.ا", 0},
+ {"TWD", "Taiwan Dollar", "NT$", 0},
+
+ {"BTC", "Bitcoin", "₿", 1},
+ {"ETH", "Ethereum", "Ξ", 1},
+ {"USDT", "Tether", "₮", 1},
+ {"BNB", "Binance Coin", "BNB", 1},
+ {"XRP", "Ripple", "XRP", 1},
+ {"USDC", "USD Coin", "$", 1},
+ {"ADA", "Cardano", "ADA", 1},
+ {"DOGE", "Dogecoin", "Ð", 1},
+ {"SOL", "Solana", "SOL", 1},
+ {"TRX", "Tron", "TRX", 1},
+ {"DOT", "Polkadot", "DOT", 1},
+ {"MATIC", "Polygon", "MATIC", 1},
+ {"LTC", "Litecoin", "Ł", 1},
+ {"SHIB", "Shiba Inu", "SHIB", 1},
+ {"AVAX", "Avalanche", "AVAX", 1},
+ {"LINK", "Chainlink", "LINK", 1},
+ {"ATOM", "Cosmos", "ATOM", 1},
+ {"XMR", "Monero", "XMR", 1},
+ {"ETC", "Ethereum Classic", "ETC", 1},
+ {"XLM", "Stellar", "XLM", 1},
+ {"BCH", "Bitcoin Cash", "BCH", 1},
+ {"ALGO", "Algorand", "ALGO", 1},
+ {"VET", "VeChain", "VET", 1},
+ {"FIL", "Filecoin", "FIL", 1},
+ {"NEAR", "NEAR Protocol", "NEAR", 1},
+ {"APT", "Aptos", "APT", 1},
+ {"ARB", "Arbitrum", "ARB", 1},
+ {"OP", "Optimism", "OP", 1},
+ {"SAND", "The Sandbox", "SAND", 1},
+ {"MANA", "Decentraland", "MANA", 1},
+ {"AXS", "Axie Infinity", "AXS", 1},
+ {"AAVE", "Aave", "AAVE", 1},
+ {"MKR", "Maker", "MKR", 1},
+ {"GRT", "The Graph", "GRT", 1},
+ {"FTM", "Fantom", "FTM", 1},
+ {"CRO", "Cronos", "CRO", 1},
+ {"NEAR", "NEAR Protocol", "NEAR", 1},
+ {"INJ", "Injective", "INJ", 1},
+ {"RUNE", "THORChain", "RUNE", 1},
+ {"LDO", "Lido DAO", "LDO", 1},
+ {"QNT", "Quant", "QNT", 1},
+ {"RNDR", "Render", "RNDR", 1},
+ {"STX", "Stacks", "STX", 1},
+ {"IMX", "Immutable X", "IMX", 1},
+ {"SNX", "Synthetix", "SNX", 1},
+ {"THETA", "Theta", "THETA", 1},
+ {"XTZ", "Tezos", "XTZ", 1},
+ {"EOS", "EOS", "EOS", 1},
+ {"FLOW", "Flow", "FLOW", 1},
+ {"CHZ", "Chiliz", "CHZ", 1},
+ {"CRV", "Curve DAO", "CRV", 1},
+ {"APE", "ApeCoin", "APE", 1},
+ {"PEPE", "Pepe", "PEPE", 1},
+ {"WIF", "dogwifhat", "WIF", 1},
+ {"SUI", "Sui", "SUI", 1},
+ {"SEI", "Sei", "SEI", 1},
+ {"TIA", "Celestia", "TIA", 1},
+ {"PYTH", "Pyth Network", "PYTH", 1},
+ {"BONK", "Bonk", "BONK", 1},
+ {"FET", "Fetch.ai", "FET", 1},
+ {"AGIX", "SingularityNET", "AGIX", 1},
+ {"RNDR", "Render Token", "RNDR", 1},
+};
+
+static const int CURRENCY_COUNT = sizeof(CURRENCIES) / sizeof(CURRENCIES[0]);
+
+static int is_whitespace(char c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+static const CurrencyDef *find_currency(const char *str) {
+ if (!str || !*str)
+ return NULL;
+
+ size_t len = strlen(str);
+ if (len < 2 || len > 10)
+ return NULL;
+
+ char normalized[16] = {0};
+ size_t j = 0;
+ for (size_t i = 0; i < len && j < 15; i++) {
+ normalized[j++] = toupper((unsigned char)str[i]);
+ }
+ normalized[j] = '\0';
+
+ for (int i = 0; i < CURRENCY_COUNT; i++) {
+ if (strcmp(normalized, CURRENCIES[i].code) == 0) {
+ return &CURRENCIES[i];
+ }
+ }
+ return NULL;
+}
+
+int is_currency_query(const char *query) {
+ if (!query)
+ return 0;
+
+ const char *patterns[] = {" to ", " in ", " into ", " = ", " equals ", NULL};
+
+ int has_pattern = 0;
+ for (int i = 0; patterns[i]; i++) {
+ if (strstr(query, patterns[i])) {
+ has_pattern = 1;
+ break;
+ }
+ }
+
+ if (!has_pattern) {
+ const char *last_space = strrchr(query, ' ');
+ if (last_space) {
+ const CurrencyDef *c = find_currency(last_space + 1);
+ if (c) {
+ const char *before = query;
+ while (*before && is_whitespace(*before))
+ before++;
+ const char *num_end = before;
+ while (*num_end &&
+ (isdigit(*num_end) || *num_end == '.' || *num_end == ',' ||
+ *num_end == '-' || *num_end == '+')) {
+ num_end++;
+ }
+ if (num_end > before)
+ has_pattern = 1;
+ }
+ }
+ }
+
+ return has_pattern;
+}
+
+static double parse_value(const char **ptr) {
+ const char *p = *ptr;
+ while (*p && is_whitespace(*p))
+ p++;
+
+ double value = 0.0;
+ int has_decimal = 0;
+ double decimal_place = 1.0;
+
+ if (*p == '-' || *p == '+')
+ p++;
+
+ while (*p >= '0' && *p <= '9') {
+ value = value * 10 + (*p - '0');
+ if (has_decimal)
+ decimal_place *= 10;
+ p++;
+ }
+
+ if (*p == '.' || *p == ',') {
+ has_decimal = 1;
+ p++;
+ while (*p >= '0' && *p <= '9') {
+ value = value * 10 + (*p - '0');
+ decimal_place *= 10;
+ p++;
+ }
+ }
+
+ if (has_decimal)
+ value /= decimal_place;
+
+ *ptr = p;
+ return value;
+}
+
+static int parse_currency_query(const char *query, double *value,
+ const CurrencyDef **from_curr,
+ const CurrencyDef **to_curr) {
+ *value = 0;
+ *from_curr = NULL;
+ *to_curr = NULL;
+
+ const char *value_end = query;
+ *value = parse_value(&value_end);
+
+ if (value_end == query)
+ return 0;
+
+ const char *p = value_end;
+ while (*p && is_whitespace(*p))
+ p++;
+
+ size_t remaining = strlen(p);
+ if (remaining < 2)
+ return 0;
+
+ const char *to_keywords[] = {" to ", " in ", " into ", " -> ",
+ " → ", " = ", NULL};
+ const char *to_pos = NULL;
+ size_t keyword_len = 0;
+ for (int i = 0; to_keywords[i]; i++) {
+ const char *found = strstr(p, to_keywords[i]);
+ if (found) {
+ to_pos = found + strlen(to_keywords[i]);
+ keyword_len = strlen(to_keywords[i]);
+ break;
+ }
+ }
+
+ if (!to_pos) {
+ const char *last_space = strrchr(p, ' ');
+ if (last_space && last_space > p) {
+ char from_part[32] = {0};
+ size_t len = last_space - p;
+ if (len < 31) {
+ strncpy(from_part, p, len);
+ *from_curr = find_currency(from_part);
+ if (*from_curr) {
+ *to_curr = find_currency(last_space + 1);
+ return *to_curr ? 1 : 0;
+ }
+ }
+ }
+ return 0;
+ }
+
+ char from_part[32] = {0};
+ size_t from_len = to_pos - p - keyword_len;
+ if (from_len > 31)
+ from_len = 31;
+ strncpy(from_part, p, from_len);
+
+ char *end_from = from_part + from_len;
+ while (end_from > from_part && is_whitespace(end_from[-1]))
+ end_from--;
+ *end_from = '\0';
+
+ *from_curr = find_currency(from_part);
+ if (!*from_curr) {
+ return 0;
+ }
+
+ while (*to_pos && is_whitespace(*to_pos))
+ to_pos++;
+
+ if (!*to_pos)
+ return 0;
+
+ char to_part[32] = {0};
+ size_t to_len = 0;
+ const char *tp = to_pos;
+ while (*tp && !is_whitespace(*tp) && to_len < 31) {
+ to_part[to_len++] = *tp++;
+ }
+ to_part[to_len] = '\0';
+
+ *to_curr = find_currency(to_part);
+ if (!*to_curr) {
+ char try_buf[32] = {0};
+ strncpy(try_buf, to_pos, 31);
+ *to_curr = find_currency(try_buf);
+ }
+
+ return *to_curr ? 1 : 0;
+}
+
+static double get_rate_from_json(const char *json, const char *target_code) {
+ JsonFloatMap map;
+ if (json_parse_float_map(json, "rates", &map)) {
+ for (size_t i = 0; i < map.count; i++) {
+ if (strcmp(map.keys[i], target_code) == 0) {
+ return map.values[i];
+ }
+ }
+ }
+ return 0;
+}
+
+static void format_number(double val, char *buf, size_t bufsize) {
+ if (bufsize == 0)
+ return;
+ if (val == 0) {
+ snprintf(buf, bufsize, "0");
+ return;
+ }
+ if (fabs(val) < 0.01 && fabs(val) > 0) {
+ snprintf(buf, bufsize, "%.6f", val);
+ } else if (fabs(val) < 1) {
+ snprintf(buf, bufsize, "%.4f", val);
+ char *p = buf + strlen(buf) - 1;
+ while (p > buf && (*p == '0' || *p == '.')) {
+ if (*p == '.')
+ break;
+ *p-- = '\0';
+ }
+ } else if (fmod(val + 0.0001, 1.0) < 0.0002) {
+ snprintf(buf, bufsize, "%.0f", val);
+ } else {
+ snprintf(buf, bufsize, "%.2f", val);
+ char *p = buf + strlen(buf) - 1;
+ while (p > buf && (*p == '0' || *p == '.')) {
+ if (*p == '.')
+ break;
+ *p-- = '\0';
+ }
+ }
+}
+
+static char *build_html(double value, const CurrencyDef *from,
+ const CurrencyDef *to, double result) {
+ static char html[4096];
+ char val_buf[64], res_buf[64];
+
+ format_number(value, val_buf, sizeof(val_buf));
+ format_number(result, res_buf, sizeof(res_buf));
+
+ snprintf(html, sizeof(html),
+ "<div class='currency-conv-container' style='line-height: 1.6;'>"
+ "<div style='font-size: 1.3em; margin-bottom: 8px;'>"
+ "<b>%s %s</b> = <b>%s %s</b></div>"
+ "<div style='font-size: 0.9em; color: #666;'>"
+ "1 %s = %.4f %s</div>"
+ "</div>",
+ val_buf, from->code, res_buf, to->code, from->code, result / value,
+ to->code);
+
+ return html;
+}
+
+InfoBox fetch_currency_data(const char *query) {
+ InfoBox info = {NULL, NULL, NULL, NULL};
+ if (!query)
+ return info;
+
+ double value = 0;
+ const CurrencyDef *from_curr = NULL;
+ const CurrencyDef *to_curr = NULL;
+
+ if (!parse_currency_query(query, &value, &from_curr, &to_curr))
+ return info;
+ if (!from_curr || !to_curr)
+ return info;
+ if (strcmp(from_curr->code, to_curr->code) == 0)
+ return info;
+
+ char cache_key[128];
+ snprintf(cache_key, sizeof(cache_key), "currency_%s_%s", from_curr->code,
+ to_curr->code);
+
+ char *cached_data = NULL;
+ size_t cached_size = 0;
+ double rate = 0;
+ int use_cache = 0;
+
+ int is_crypto = from_curr->is_crypto || to_curr->is_crypto;
+
+ if (get_cache_ttl_infobox() > 0) {
+ if (cache_get(cache_key, get_cache_ttl_infobox(), &cached_data,
+ &cached_size) == 0 &&
+ cached_data && cached_size > 0) {
+ if (is_crypto) {
+ rate = json_get_float(cached_data, to_curr->code);
+ } else {
+ rate = get_rate_from_json(cached_data, to_curr->code);
+ }
+ if (rate > 0) {
+ use_cache = 1;
+ }
+ free(cached_data);
+ }
+ }
+
+ if (!use_cache) {
+ char url[512];
+
+ if (is_crypto) {
+ snprintf(url, sizeof(url),
+ "https://min-api.cryptocompare.com/data/price?fsym=%s&tsyms=%s",
+ from_curr->code, to_curr->code);
+ } else {
+ snprintf(url, sizeof(url),
+ "https://api.exchangerate-api.com/v4/latest/%s",
+ from_curr->code);
+ }
+
+ HttpResponse resp = http_get(url, "libcurl-agent/1.0");
+ if (resp.memory && resp.size > 0) {
+ if (is_crypto) {
+ rate = json_get_float(resp.memory, to_curr->code);
+ } else {
+ rate = get_rate_from_json(resp.memory, to_curr->code);
+ }
+ if (rate > 0 && get_cache_ttl_infobox() > 0) {
+ cache_set(cache_key, resp.memory, resp.size);
+ }
+ }
+ http_response_free(&resp);
+ }
+
+ if (rate <= 0)
+ return info;
+
+ double result = value * rate;
+
+ info.title = strdup("Currency Conversion");
+ info.extract = strdup(build_html(value, from_curr, to_curr, result));
+ info.thumbnail_url = strdup("/static/calculation.svg");
+ info.url = strdup("#");
+
+ return info;
+}
diff --git a/src/Infobox/CurrencyConversion.h b/src/Infobox/CurrencyConversion.h
new file mode 100644
index 0000000..f258fbe
--- /dev/null
+++ b/src/Infobox/CurrencyConversion.h
@@ -0,0 +1,9 @@
+#ifndef CURRENCYCONVERSION_H
+#define CURRENCYCONVERSION_H
+
+#include "Infobox.h"
+
+int is_currency_query(const char *query);
+InfoBox fetch_currency_data(const char *query);
+
+#endif
diff --git a/src/Routes/Search.c b/src/Routes/Search.c
index dcfdf42..7d62f08 100644
--- a/src/Routes/Search.c
+++ b/src/Routes/Search.c
@@ -1,5 +1,6 @@
#include "Search.h"
#include "../Infobox/Calculator.h"
+#include "../Infobox/CurrencyConversion.h"
#include "../Infobox/Dictionary.h"
#include "../Infobox/UnitConversion.h"
#include "../Infobox/Wikipedia.h"
@@ -129,6 +130,20 @@ static void *unit_thread_func(void *arg) {
return NULL;
}
+static void *currency_thread_func(void *arg) {
+ InfoBoxThreadData *data = (InfoBoxThreadData *)arg;
+
+ if (is_currency_query(data->query)) {
+ data->result = fetch_currency_data(data->query);
+ data->success =
+ (data->result.title != NULL && data->result.extract != NULL);
+ } else {
+ data->success = 0;
+ }
+
+ return NULL;
+}
+
static int add_infobox_to_collection(InfoBox *infobox, char ****collection,
int **inner_counts, int current_count) {
*collection =
@@ -182,17 +197,19 @@ int results_handler(UrlParams *params) {
return -1;
}
- pthread_t wiki_tid, calc_tid, dict_tid, unit_tid;
+ pthread_t wiki_tid, calc_tid, dict_tid, unit_tid, currency_tid;
InfoBoxThreadData wiki_data = {.query = raw_query, .success = 0};
InfoBoxThreadData calc_data = {.query = raw_query, .success = 0};
InfoBoxThreadData dict_data = {.query = raw_query, .success = 0};
InfoBoxThreadData unit_data = {.query = raw_query, .success = 0};
+ InfoBoxThreadData currency_data = {.query = raw_query, .success = 0};
if (page == 1) {
pthread_create(&wiki_tid, NULL, wiki_thread_func, &wiki_data);
pthread_create(&calc_tid, NULL, calc_thread_func, &calc_data);
pthread_create(&dict_tid, NULL, dict_thread_func, &dict_data);
pthread_create(&unit_tid, NULL, unit_thread_func, &unit_data);
+ pthread_create(&currency_tid, NULL, currency_thread_func, &currency_data);
}
ScrapeJob jobs[ENGINE_COUNT];
@@ -219,6 +236,7 @@ int results_handler(UrlParams *params) {
pthread_join(calc_tid, NULL);
pthread_join(dict_tid, NULL);
pthread_join(unit_tid, NULL);
+ pthread_join(currency_tid, NULL);
}
char ***infobox_matrix = NULL;
@@ -244,6 +262,12 @@ int results_handler(UrlParams *params) {
&infobox_inner_counts, infobox_count);
}
+ if (currency_data.success) {
+ infobox_count =
+ add_infobox_to_collection(&currency_data.result, &infobox_matrix,
+ &infobox_inner_counts, infobox_count);
+ }
+
if (wiki_data.success) {
infobox_count =
add_infobox_to_collection(&wiki_data.result, &infobox_matrix,
@@ -353,6 +377,8 @@ int results_handler(UrlParams *params) {
free_infobox(&dict_data.result);
if (unit_data.success)
free_infobox(&unit_data.result);
+ if (currency_data.success)
+ free_infobox(&currency_data.result);
}
free_context(&ctx);
diff --git a/src/Utility/JsonHelper.c b/src/Utility/JsonHelper.c
new file mode 100644
index 0000000..1eb4098
--- /dev/null
+++ b/src/Utility/JsonHelper.c
@@ -0,0 +1,139 @@
+#include "JsonHelper.h"
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int is_whitespace_json(char c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+int json_parse_float_map(const char *json, const char *target_key,
+ JsonFloatMap *result) {
+ memset(result, 0, sizeof(JsonFloatMap));
+
+ if (!json || !target_key)
+ return 0;
+
+ const char *obj_start = strchr(json, '{');
+ if (!obj_start)
+ return 0;
+
+ const char *rates_start = strstr(obj_start, target_key);
+ if (!rates_start)
+ return 0;
+
+ rates_start = strchr(rates_start, '{');
+ if (!rates_start)
+ return 0;
+
+ const char *p = rates_start + 1;
+ while (*p && *p != '}') {
+ while (*p && (*p == ' ' || *p == '\n' || *p == '\t' || *p == ','))
+ p++;
+
+ if (*p == '}')
+ break;
+
+ if (*p != '"')
+ break;
+ p++;
+
+ char key[32] = {0};
+ size_t key_len = 0;
+ while (*p && *p != '"' && key_len < 31) {
+ key[key_len++] = *p++;
+ }
+ if (*p != '"')
+ break;
+ p++;
+
+ while (*p && *p != ':')
+ p++;
+ if (*p != ':')
+ break;
+ p++;
+
+ while (*p && is_whitespace_json(*p))
+ p++;
+
+ double value = 0;
+ int has_digit = 0;
+ while (*p >= '0' && *p <= '9') {
+ value = value * 10 + (*p - '0');
+ has_digit = 1;
+ p++;
+ }
+ if (*p == '.') {
+ p++;
+ double frac = 0.1;
+ while (*p >= '0' && *p <= '9') {
+ value += (*p - '0') * frac;
+ frac *= 0.1;
+ has_digit = 1;
+ p++;
+ }
+ }
+
+ if (has_digit && key_len > 0) {
+ memcpy(result->keys[result->count], key, key_len);
+ result->keys[result->count][key_len] = '\0';
+ result->values[result->count] = value;
+ result->count++;
+ if (result->count >= 256)
+ break;
+ }
+ }
+
+ return result->count > 0;
+}
+
+double json_get_float(const char *json, const char *key) {
+ if (!json || !key)
+ return 0;
+
+ const char *key_pos = strstr(json, key);
+ if (!key_pos)
+ return 0;
+
+ const char *colon = strchr(key_pos, ':');
+ if (!colon)
+ return 0;
+
+ colon++;
+ while (*colon && is_whitespace_json(*colon))
+ colon++;
+
+ return strtod(colon, NULL);
+}
+
+char *json_get_string(const char *json, const char *key) {
+ if (!json || !key)
+ return NULL;
+
+ static char buffer[256];
+
+ const char *key_pos = strstr(json, key);
+ if (!key_pos)
+ return NULL;
+
+ const char *colon = strchr(key_pos, ':');
+ if (!colon)
+ return NULL;
+
+ colon++;
+ while (*colon && is_whitespace_json(*colon))
+ colon++;
+
+ if (*colon != '"')
+ return NULL;
+ colon++;
+
+ size_t len = 0;
+ while (*colon && *colon != '"' && len < 255) {
+ buffer[len++] = *colon++;
+ }
+ buffer[len] = '\0';
+
+ return buffer;
+}
diff --git a/src/Utility/JsonHelper.h b/src/Utility/JsonHelper.h
new file mode 100644
index 0000000..62034b8
--- /dev/null
+++ b/src/Utility/JsonHelper.h
@@ -0,0 +1,17 @@
+#ifndef JSONHELPER_H
+#define JSONHELPER_H
+
+#include <stddef.h>
+
+typedef struct {
+ double values[256];
+ char keys[256][32];
+ size_t count;
+} JsonFloatMap;
+
+int json_parse_float_map(const char *json, const char *target_key,
+ JsonFloatMap *result);
+double json_get_float(const char *json, const char *key);
+char *json_get_string(const char *json, const char *key);
+
+#endif