UnitConversion.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #include "UnitConversion.h"
  2. #include <ctype.h>
  3. #include <math.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. typedef enum {
  8. UNIT_LENGTH,
  9. UNIT_WEIGHT,
  10. UNIT_TEMP,
  11. UNIT_VOLUME,
  12. UNIT_AREA,
  13. UNIT_TIME,
  14. UNIT_UNKNOWN
  15. } UnitType;
  16. typedef struct {
  17. const char *name;
  18. const char *alias[4];
  19. UnitType type;
  20. double to_base;
  21. } UnitDef;
  22. static const UnitDef UNITS[] = {
  23. {"metre", {"m", "metres", "meter", "meters"}, UNIT_LENGTH, 1.0},
  24. {"kilometre", {"km", "kilometres", "kilometer", "kilometers"}, UNIT_LENGTH, 1000.0},
  25. {"centimetre", {"cm", "centimetres", "centimeter", "centimeters"}, UNIT_LENGTH, 0.01},
  26. {"millimetre", {"mm", "millimetres", "millimeter", "millimeters"}, UNIT_LENGTH, 0.001},
  27. {"mile", {"mi", "miles"}, UNIT_LENGTH, 1609.344},
  28. {"yard", {"yd", "yards"}, UNIT_LENGTH, 0.9144},
  29. {"foot", {"ft", "feet", "'"}, UNIT_LENGTH, 0.3048},
  30. {"inch", {"in", "inches", "\""}, UNIT_LENGTH, 0.0254},
  31. {"kilogram", {"kg", "kilograms", "kilo", "kilos"}, UNIT_WEIGHT, 1.0},
  32. {"gram", {"g", "grams"}, UNIT_WEIGHT, 0.001},
  33. {"milligram", {"mg", "milligrams"}, UNIT_WEIGHT, 0.000001},
  34. {"pound", {"lb", "lbs", "pounds"}, UNIT_WEIGHT, 0.453592},
  35. {"ounce", {"oz", "ounces"}, UNIT_WEIGHT, 0.0283495},
  36. {"tonne", {"tonnes", "tons", "ton"}, UNIT_WEIGHT, 1000.0},
  37. {"stone", {"st", "stones"}, UNIT_WEIGHT, 6.35029},
  38. {"celsius", {"c", "°c", "degrees celsius", "degrees c"}, UNIT_TEMP, 1.0},
  39. {"fahrenheit", {"f", "°f", "degrees fahrenheit", "degrees f"}, UNIT_TEMP, 1.0},
  40. {"kelvin", {"k", "degrees kelvin", "degrees k"}, UNIT_TEMP, 1.0},
  41. {"litre", {"l", "litres", "liter", "liters"}, UNIT_VOLUME, 1.0},
  42. {"millilitre", {"ml", "millilitres", "milliliter", "milliliters"}, UNIT_VOLUME, 0.001},
  43. {"gallon", {"gal", "gallons"}, UNIT_VOLUME, 3.78541},
  44. {"quart", {"qt", "quarts"}, UNIT_VOLUME, 0.946353},
  45. {"pint", {"pt", "pints"}, UNIT_VOLUME, 0.473176},
  46. {"cup", {"cups"}, UNIT_VOLUME, 0.236588},
  47. {"fluid ounce", {"fl oz", "fluid ounces"}, UNIT_VOLUME, 0.0295735},
  48. {"square metre", {"sqm", "sq m", "m2", "square metres"}, UNIT_AREA, 1.0},
  49. {"square foot", {"sqft", "sq ft", "ft2", "square feet"}, UNIT_AREA, 0.092903},
  50. {"square kilometre", {"sqkm", "sq km", "km2", "square kilometres"}, UNIT_AREA, 1000000.0},
  51. {"square mile", {"sqmi", "sq mi", "mi2", "square miles"}, UNIT_AREA, 2589988.0},
  52. {"acre", {"acres"}, UNIT_AREA, 4046.86},
  53. {"hectare", {"ha", "hectares"}, UNIT_AREA, 10000.0},
  54. {"second", {"sec", "seconds", "s"}, UNIT_TIME, 1.0},
  55. {"minute", {"min", "minutes"}, UNIT_TIME, 60.0},
  56. {"hour", {"hr", "hours", "h"}, UNIT_TIME, 3600.0},
  57. {"day", {"days", "d"}, UNIT_TIME, 86400.0},
  58. {"week", {"weeks", "wk"}, UNIT_TIME, 604800.0},
  59. {"month", {"months", "mo"}, UNIT_TIME, 2629746.0},
  60. {"year", {"years", "yr"}, UNIT_TIME, 31556952.0},
  61. };
  62. static const int UNIT_COUNT = sizeof(UNITS) / sizeof(UNITS[0]);
  63. static int is_whitespace(char c) {
  64. return c == ' ' || c == '\t' || c == '\n' || c == '\r';
  65. }
  66. static const UnitDef *find_unit(const char *str) {
  67. if (!str || !*str) return NULL;
  68. size_t len = strlen(str);
  69. char normalized[64] = {0};
  70. size_t j = 0;
  71. for (size_t i = 0; i < len && j < 63; i++) {
  72. if ((unsigned char)str[i] == 0xC2 && (unsigned char)str[i+1] == 0xB0) {
  73. i++;
  74. continue;
  75. }
  76. if (str[i] == '^' && i + 1 < len && str[i + 1] == '2') {
  77. normalized[j++] = '2';
  78. i++;
  79. continue;
  80. }
  81. normalized[j++] = tolower((unsigned char)str[i]);
  82. }
  83. normalized[j] = '\0';
  84. for (int i = 0; i < UNIT_COUNT; i++) {
  85. if (strcmp(normalized, UNITS[i].name) == 0) return &UNITS[i];
  86. for (int k = 0; k < 4 && UNITS[i].alias[k]; k++) {
  87. if (strcmp(normalized, UNITS[i].alias[k]) == 0) return &UNITS[i];
  88. }
  89. }
  90. return NULL;
  91. }
  92. int is_unit_conv_query(const char *query) {
  93. if (!query) return 0;
  94. const char *patterns[] = {
  95. " to ", " in ", " into ",
  96. " = ", " equals ", " equal ",
  97. " convert ", " conversion ",
  98. " -> ", " → ",
  99. NULL
  100. };
  101. int has_pattern = 0;
  102. for (int i = 0; patterns[i]; i++) {
  103. if (strstr(query, patterns[i])) {
  104. has_pattern = 1;
  105. break;
  106. }
  107. }
  108. if (!has_pattern) {
  109. const char *last_space = strrchr(query, ' ');
  110. if (last_space) {
  111. const UnitDef *u = find_unit(last_space + 1);
  112. if (u) {
  113. const char *before = query;
  114. while (*before && is_whitespace(*before)) before++;
  115. const char *num_end = before;
  116. while (*num_end &&
  117. (isdigit(*num_end) || *num_end == '.' || *num_end == '-' ||
  118. *num_end == '+' || *num_end == '/' || *num_end == '\'' || *num_end == '"')) {
  119. num_end++;
  120. }
  121. if (num_end > before) has_pattern = 1;
  122. }
  123. }
  124. }
  125. return has_pattern;
  126. }
  127. static double parse_value(const char **ptr) {
  128. const char *p = *ptr;
  129. while (*p && is_whitespace(*p)) p++;
  130. double value = 0.0;
  131. int has_num = 0;
  132. if (*p == '-' || *p == '+') p++;
  133. while (*p >= '0' && *p <= '9') {
  134. value = value * 10 + (*p - '0');
  135. has_num = 1;
  136. p++;
  137. }
  138. if (*p == '.') {
  139. p++;
  140. double frac = 0.1;
  141. while (*p >= '0' && *p <= '9') {
  142. value += (*p - '0') * frac;
  143. frac *= 0.1;
  144. has_num = 1;
  145. p++;
  146. }
  147. }
  148. if (*p == '/' && has_num) {
  149. p++;
  150. double denom = 0.0;
  151. int has_denom = 0;
  152. while (*p >= '0' && *p <= '9') {
  153. denom = denom * 10 + (*p - '0');
  154. has_denom = 1;
  155. p++;
  156. }
  157. if (has_denom && denom > 0) {
  158. value = value / denom;
  159. }
  160. }
  161. while (*p == '\'' || *p == '"') {
  162. double extra = 0.0;
  163. p++;
  164. while (*p >= '0' && *p <= '9') {
  165. extra = extra * 10 + (*p - '0');
  166. p++;
  167. }
  168. if (*p == '.') {
  169. p++;
  170. double frac = 0.1;
  171. while (*p >= '0' && *p <= '9') {
  172. extra += (*p - '0') * frac;
  173. frac *= 0.1;
  174. p++;
  175. }
  176. }
  177. if (*p == '\'' || *p == '"') p++;
  178. value += extra * (p[-1] == '\'' ? 0.3048 : 0.0254);
  179. }
  180. if (!has_num) {
  181. *ptr = p;
  182. return 0.0;
  183. }
  184. *ptr = p;
  185. return value;
  186. }
  187. static int is_separator(char c) {
  188. return is_whitespace(c) || c == ',' || c == '.' || c == '(' || c == ')' || c == '\0';
  189. }
  190. static int parse_conversion_query(const char *query, double *value, const UnitDef **from_unit, const UnitDef **to_unit) {
  191. *value = 0;
  192. *from_unit = NULL;
  193. *to_unit = NULL;
  194. const char *value_end = query;
  195. *value = parse_value(&value_end);
  196. if (value_end == query) return 0;
  197. const char *p = value_end;
  198. while (*p && is_whitespace(*p)) p++;
  199. size_t remaining = strlen(p);
  200. if (remaining < 2) return 0;
  201. const char *to_keywords[] = {" to ", " in ", " into ", " -> ", " → ", " = ", NULL};
  202. const char *to_pos = NULL;
  203. size_t keyword_len = 0;
  204. for (int i = 0; to_keywords[i]; i++) {
  205. const char *found = strstr(p, to_keywords[i]);
  206. if (found) {
  207. to_pos = found + strlen(to_keywords[i]);
  208. keyword_len = strlen(to_keywords[i]);
  209. break;
  210. }
  211. }
  212. if (!to_pos) {
  213. const char *last_space = strrchr(p, ' ');
  214. if (last_space && last_space > p) {
  215. char from_part[64] = {0};
  216. size_t len = last_space - p;
  217. if (len < 63) {
  218. strncpy(from_part, p, len);
  219. *from_unit = find_unit(from_part);
  220. if (*from_unit) {
  221. *to_unit = find_unit(last_space + 1);
  222. return *to_unit ? 1 : 0;
  223. }
  224. }
  225. }
  226. return 0;
  227. }
  228. char from_part[64] = {0};
  229. size_t from_len = to_pos - p - keyword_len;
  230. if (from_len > 63) from_len = 63;
  231. strncpy(from_part, p, from_len);
  232. char *end_from = from_part + from_len;
  233. while (end_from > from_part && is_whitespace(end_from[-1])) end_from--;
  234. *end_from = '\0';
  235. *from_unit = find_unit(from_part);
  236. if (!*from_unit) {
  237. char *end = from_part + strlen(from_part);
  238. while (end > from_part) {
  239. while (end > from_part && is_whitespace(end[-1])) end--;
  240. if (end <= from_part) break;
  241. char *start = end;
  242. while (start > from_part && !is_whitespace(start[-1])) start--;
  243. size_t word_len = end - start;
  244. memmove(from_part + word_len + 1, from_part, start - from_part);
  245. from_part[word_len] = ' ';
  246. from_part[word_len + 1] = '\0';
  247. *from_unit = find_unit(from_part);
  248. if (*from_unit) break;
  249. end = start;
  250. }
  251. }
  252. if (!*from_unit) return 0;
  253. while (*to_pos && is_whitespace(*to_pos)) to_pos++;
  254. if (!*to_pos) return 0;
  255. char to_part[64] = {0};
  256. size_t to_len = 0;
  257. const char *tp = to_pos;
  258. while (*tp && !is_separator(*tp) && to_len < 63) {
  259. to_part[to_len++] = *tp++;
  260. }
  261. to_part[to_len] = '\0';
  262. *to_unit = find_unit(to_part);
  263. if (!*to_unit) {
  264. const char *try_ptr = to_pos;
  265. while (*try_ptr && is_whitespace(*try_ptr)) try_ptr++;
  266. char try_buf[64] = {0};
  267. size_t try_len = 0;
  268. while (*try_ptr && try_len < 63) {
  269. try_buf[try_len++] = *try_ptr++;
  270. }
  271. while (try_len > 0) {
  272. *to_unit = find_unit(try_buf);
  273. if (*to_unit) {
  274. strcpy(to_part, try_buf);
  275. break;
  276. }
  277. char *last_space = strrchr(try_buf, ' ');
  278. if (!last_space) break;
  279. *last_space = '\0';
  280. try_len = strlen(try_buf);
  281. }
  282. }
  283. return *to_unit ? 1 : 0;
  284. }
  285. static double convert_temp(double value, const UnitDef *from, const UnitDef *to) {
  286. double celsius = 0;
  287. if (strcmp(from->name, "celsius") == 0) celsius = value;
  288. else if (strcmp(from->name, "fahrenheit") == 0) celsius = (value - 32) * 5.0 / 9.0;
  289. else if (strcmp(from->name, "kelvin") == 0) celsius = value - 273.15;
  290. if (strcmp(to->name, "celsius") == 0) return celsius;
  291. else if (strcmp(to->name, "fahrenheit") == 0) return celsius * 9.0 / 5.0 + 32;
  292. else if (strcmp(to->name, "kelvin") == 0) return celsius + 273.15;
  293. return 0;
  294. }
  295. static double convert_value(double value, const UnitDef *from, const UnitDef *to) {
  296. if (from->type != to->type) return 0;
  297. if (from->type == UNIT_TEMP) {
  298. return convert_temp(value, from, to);
  299. }
  300. double base_value = value * from->to_base;
  301. return base_value / to->to_base;
  302. }
  303. static void format_number(double val, char *buf, size_t bufsize) {
  304. if (bufsize == 0) return;
  305. if (val == 0) {
  306. snprintf(buf, bufsize, "0");
  307. return;
  308. }
  309. if (fabs(val) < 0.01 && fabs(val) > 0) {
  310. snprintf(buf, bufsize, "%.2g", val);
  311. } else if (fabs(val) < 1) {
  312. snprintf(buf, bufsize, "%.2f", val);
  313. char *p = buf + strlen(buf) - 1;
  314. while (p > buf && *p == '0') *p-- = '\0';
  315. if (*p == '.') *p = '\0';
  316. } else if (fmod(val + 0.0001, 1.0) < 0.0002) {
  317. snprintf(buf, bufsize, "%.0f", val);
  318. } else {
  319. snprintf(buf, bufsize, "%.2f", val);
  320. char *p = buf + strlen(buf) - 1;
  321. while (p > buf && *p == '0') *p-- = '\0';
  322. if (*p == '.') *p = '\0';
  323. }
  324. }
  325. static const char *pluralize(const char *unit, double value, char *buf, size_t bufsize) {
  326. int is_one = (fabs(value - 1.0) < 0.0001 || fabs(value + 1.0) < 0.0001);
  327. size_t len = strlen(unit);
  328. if (len == 0 || bufsize == 0) return unit;
  329. strncpy(buf, unit, bufsize - 1);
  330. buf[bufsize - 1] = '\0';
  331. if (strcmp(unit, "foot") == 0 || strcmp(unit, "square foot") == 0) {
  332. if (is_one) strcpy(buf, unit);
  333. else strcpy(buf, strcmp(unit, "square foot") == 0 ? "square feet" : "feet");
  334. return buf;
  335. }
  336. if (strcmp(unit, "inch") == 0 || strcmp(unit, "square inch") == 0) {
  337. if (is_one) strcpy(buf, unit);
  338. else strcpy(buf, strcmp(unit, "square inch") == 0 ? "square inches" : "inches");
  339. return buf;
  340. }
  341. if (strcmp(unit, "stone") == 0) {
  342. if (is_one) strcpy(buf, "stone");
  343. else strcpy(buf, "stones");
  344. return buf;
  345. }
  346. if (strcmp(unit, "celsius") == 0 ||
  347. strcmp(unit, "fahrenheit") == 0 ||
  348. strcmp(unit, "kelvin") == 0) {
  349. strcpy(buf, unit);
  350. return buf;
  351. }
  352. if (unit[len-1] == 's' ||
  353. unit[len-1] == 'x' ||
  354. unit[len-1] == 'z' ||
  355. (len >= 2 && unit[len-2] == 'c' && unit[len-1] == 'h') ||
  356. (len >= 2 && unit[len-2] == 's' && unit[len-1] == 'h')) {
  357. if (!is_one) {
  358. buf[len] = 'e';
  359. buf[len+1] = '\0';
  360. }
  361. } else if (unit[len-1] == 'y' && len >= 2 &&
  362. !(unit[len-2] == 'a' || unit[len-2] == 'e' ||
  363. unit[len-2] == 'i' || unit[len-2] == 'o' ||
  364. unit[len-2] == 'u')) {
  365. if (is_one) {
  366. buf[len-1] = '\0';
  367. } else {
  368. buf[len] = 's';
  369. buf[len+1] = '\0';
  370. }
  371. } else if (len >= 2 && unit[len-2] == 'f' && unit[len-1] == 'e') {
  372. if (is_one) {
  373. buf[len-2] = '\0';
  374. } else {
  375. buf[len-1] = 's';
  376. buf[len] = '\0';
  377. }
  378. } else if (unit[len-1] == 'f' && len >= 1) {
  379. if (is_one) {
  380. buf[len-1] = '\0';
  381. } else {
  382. buf[len-1] = 'v';
  383. buf[len] = 'e';
  384. buf[len+1] = 's';
  385. buf[len+2] = '\0';
  386. }
  387. } else if (unit[len-1] == 'e' && len >= 2 && unit[len-2] == 'f') {
  388. if (is_one) {
  389. buf[len-2] = '\0';
  390. } else {
  391. buf[len-1] = 's';
  392. buf[len] = '\0';
  393. }
  394. } else {
  395. if (!is_one) {
  396. buf[len] = 's';
  397. buf[len+1] = '\0';
  398. }
  399. }
  400. return buf;
  401. }
  402. static char *build_html(double value, const UnitDef *from, double result, const UnitDef *to) {
  403. static char html[4096];
  404. char val_buf[64], res_buf[64], from_name_buf[64], to_name_buf[64];
  405. format_number(value, val_buf, sizeof(val_buf));
  406. format_number(result, res_buf, sizeof(res_buf));
  407. pluralize(from->name, value, from_name_buf, sizeof(from_name_buf));
  408. pluralize(to->name, result, to_name_buf, sizeof(to_name_buf));
  409. int n = snprintf(html, sizeof(html),
  410. "<div class='unit-conv-container' style='line-height: 1.6;'>"
  411. "<div style='font-size: 1.3em; margin-bottom: 8px;'>"
  412. "<b>%s %s</b> = <b>%s %s</b>"
  413. "</div>",
  414. val_buf, from_name_buf,
  415. res_buf, to_name_buf);
  416. snprintf(html + n, sizeof(html) - n, "</div>");
  417. return html;
  418. }
  419. InfoBox fetch_unit_conv_data(const char *query) {
  420. InfoBox info = {NULL, NULL, NULL, NULL};
  421. if (!query) return info;
  422. double value = 0;
  423. const UnitDef *from = NULL;
  424. const UnitDef *to = NULL;
  425. if (!parse_conversion_query(query, &value, &from, &to)) return info;
  426. if (!from || !to) return info;
  427. if (from->type != to->type) return info;
  428. double result = convert_value(value, from, to);
  429. if (result == 0 && value != 0 && from->type != UNIT_TEMP) return info;
  430. info.title = strdup("Unit Conversion");
  431. info.extract = strdup(build_html(value, from, result, to));
  432. info.thumbnail_url = strdup("/static/calculation.svg");
  433. info.url = strdup("#");
  434. return info;
  435. }