Files
MontaukOS/programs/src/wikipedia/text.cpp
T

241 lines
7.5 KiB
C++

/*
* text.cpp
* Text encoding, HTTP / URL / JSON parsing for the Wikipedia client
* Copyright (c) 2026 Daniel Hammer
*/
#include "wikipedia.h"
// ============================================================================
// Codepoint and UTF-8 helpers
// ============================================================================
int encode_single_byte_codepoint(unsigned codepoint) {
if (codepoint < 0x80) return (int)codepoint;
if (codepoint >= 0xA0 && codepoint <= 0xFF) return (int)codepoint;
switch (codepoint) {
case 0x20AC: return 0x80;
case 0x201A: return 0x82;
case 0x0192: return 0x83;
case 0x201E: return 0x84;
case 0x2026: return 0x85;
case 0x2020: return 0x86;
case 0x2021: return 0x87;
case 0x02C6: return 0x88;
case 0x2030: return 0x89;
case 0x0160: return 0x8A;
case 0x2039: return 0x8B;
case 0x0152: return 0x8C;
case 0x017D: return 0x8E;
case 0x2018: return 0x91;
case 0x2019: return 0x92;
case 0x201C: return 0x93;
case 0x201D: return 0x94;
case 0x2022: return 0x95;
case 0x2013: return 0x96;
case 0x2014: return 0x97;
case 0x02DC: return 0x98;
case 0x2122: return 0x99;
case 0x0161: return 0x9A;
case 0x203A: return 0x9B;
case 0x0153: return 0x9C;
case 0x017E: return 0x9E;
case 0x0178: return 0x9F;
default: return -1;
}
}
void append_codepoint(char* out, int* j, int maxOut, unsigned codepoint) {
if (!out || !j || *j >= maxOut - 1) return;
int encoded = encode_single_byte_codepoint(codepoint);
if (encoded >= 0) {
out[(*j)++] = (char)encoded;
return;
}
switch (codepoint) {
case 0x00A0:
out[(*j)++] = ' ';
return;
case 0x2010:
case 0x2011:
case 0x2212:
out[(*j)++] = '-';
return;
default:
out[(*j)++] = '?';
return;
}
}
void underscores_to_spaces(char* text) {
if (!text) return;
for (int i = 0; text[i]; i++) {
if (text[i] == '_') text[i] = ' ';
}
}
bool is_main_page_query(const char* query) {
if (!query) return false;
char normalized[16];
int out = 0;
for (int i = 0; query[i] && out < (int)sizeof(normalized) - 1; i++) {
char ch = query[i];
if (ch == ' ' || ch == '_' || ch == '-') continue;
if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a');
normalized[out++] = ch;
}
normalized[out] = '\0';
return strcmp(normalized, "mainpage") == 0;
}
unsigned decode_utf8_codepoint(const char* buf, int len, int* consumed) {
if (!buf || len <= 0) {
if (consumed) *consumed = 0;
return '?';
}
unsigned char c0 = (unsigned char)buf[0];
if (c0 < 0x80) {
if (consumed) *consumed = 1;
return c0;
}
auto continuation = [](unsigned char c) -> bool {
return (c & 0xC0) == 0x80;
};
if ((c0 & 0xE0) == 0xC0 && len >= 2) {
unsigned char c1 = (unsigned char)buf[1];
if (continuation(c1)) {
if (consumed) *consumed = 2;
return ((unsigned)(c0 & 0x1F) << 6) | (unsigned)(c1 & 0x3F);
}
} else if ((c0 & 0xF0) == 0xE0 && len >= 3) {
unsigned char c1 = (unsigned char)buf[1];
unsigned char c2 = (unsigned char)buf[2];
if (continuation(c1) && continuation(c2)) {
if (consumed) *consumed = 3;
return ((unsigned)(c0 & 0x0F) << 12) |
((unsigned)(c1 & 0x3F) << 6) |
(unsigned)(c2 & 0x3F);
}
} else if ((c0 & 0xF8) == 0xF0 && len >= 4) {
unsigned char c1 = (unsigned char)buf[1];
unsigned char c2 = (unsigned char)buf[2];
unsigned char c3 = (unsigned char)buf[3];
if (continuation(c1) && continuation(c2) && continuation(c3)) {
if (consumed) *consumed = 4;
return ((unsigned)(c0 & 0x07) << 18) |
((unsigned)(c1 & 0x3F) << 12) |
((unsigned)(c2 & 0x3F) << 6) |
(unsigned)(c3 & 0x3F);
}
}
if (consumed) *consumed = 1;
return '?';
}
int find_substr(const char* buf, int len, const char* needle) {
int nlen = (int)strlen(needle);
if (!buf || !needle || nlen <= 0 || nlen > len) return -1;
for (int i = 0; i <= len - nlen; i++) {
if (memcmp(buf + i, needle, nlen) == 0) return i;
}
return -1;
}
// ============================================================================
// URL encoding
// ============================================================================
int url_encode_title(const char* in, char* out, int maxLen) {
const char hex[] = "0123456789ABCDEF";
int j = 0;
for (int i = 0; in[i] && j < maxLen - 4; i++) {
char c = in[i];
if (c == ' ') {
out[j++] = '_';
} else if ((c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9')||
c=='-'||c=='_'||c=='.'||c=='~'||c=='('||c==')'||c==',') {
out[j++] = c;
} else {
out[j++] = '%';
out[j++] = hex[(unsigned char)c >> 4];
out[j++] = hex[(unsigned char)c & 0x0F];
}
}
out[j] = '\0';
return j;
}
// ============================================================================
// JSON string extraction
// ============================================================================
int extract_json_string(const char* buf, int len, const char* key,
char* out, int maxOut) {
int klen = (int)strlen(key);
for (int i = 0; i < len - klen - 3; i++) {
if (buf[i] != '"') continue;
if (memcmp(buf + i + 1, key, klen) != 0) continue;
if (buf[i + 1 + klen] != '"') continue;
if (buf[i + 2 + klen] != ':') continue;
int p = i + 3 + klen;
while (p < len && (buf[p]==' ' || buf[p]=='\t')) p++;
if (p >= len || buf[p] != '"') continue;
p++;
int j = 0;
while (p < len && j < maxOut - 4) {
if (buf[p] == '"') break;
if (buf[p] == '\\' && p + 1 < len) {
p++;
switch (buf[p]) {
case '"': out[j++] = '"'; break;
case '\\': out[j++] = '\\'; break;
case 'n': out[j++] = '\n'; break;
case 'r': break;
case 't': out[j++] = '\t'; break;
case '/': out[j++] = '/'; break;
case 'u': {
if (p + 4 < len) {
unsigned val = 0;
for (int k = 1; k <= 4; k++) {
char h = buf[p + k]; val <<= 4;
if (h>='0'&&h<='9') val |= h-'0';
else if (h>='a'&&h<='f') val |= h-'a'+10;
else if (h>='A'&&h<='F') val |= h-'A'+10;
}
p += 4;
append_codepoint(out, &j, maxOut, val);
}
break;
}
default: out[j++] = buf[p]; break;
}
} else {
unsigned char uc = (unsigned char)buf[p];
if (uc < 0x80) {
out[j++] = buf[p];
} else {
int consumed = 0;
unsigned codepoint = decode_utf8_codepoint(buf + p, len - p, &consumed);
append_codepoint(out, &j, maxOut, codepoint);
p += consumed > 0 ? consumed - 1 : 0;
}
}
p++;
}
out[j] = '\0';
return j;
}
out[0] = '\0';
return 0;
}