feat: split Wikipedia app into separate files, change icon to cog
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* 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 '?';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP parsing
|
||||
// ============================================================================
|
||||
|
||||
int find_header_end(const char* buf, int len) {
|
||||
for (int i = 0; i + 3 < len; i++)
|
||||
if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n')
|
||||
return i + 4;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int parse_status_code(const char* buf, int len) {
|
||||
int i = 0;
|
||||
while (i < len && buf[i] != ' ') i++;
|
||||
if (i >= len || i + 3 >= len) return -1;
|
||||
i++;
|
||||
if (buf[i] < '0' || buf[i] > '9') return -1;
|
||||
return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user