ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_extract_hits.c
Go to the documentation of this file.
1
11#include <string.h>
12
13#include "mdl_extract.h"
15
17typedef struct {
18 const char* name;
19 char ch;
21
36RA8_INTERNAL static bool internal_is_ws(char c)
37{
38 return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\f') || (c == '\v');
39}
40
62RA8_INTERNAL static bool
63internal_decode_entity(const char* s, size_t len, size_t* i, char* out_ch, bool* space)
64{
65 static const mdl_entity_t table[] = {
66 {"amp;", '&'},
67 {"lt;", '<'},
68 {"gt;", '>'},
69 {"quot;", '"'},
70 {"#39;", '\''},
71 {"apos;", '\''},
72 {"nbsp;", '\0'},
73 };
74 const size_t start = *i + 1U; /* byte after '&' */
75 for (size_t e = 0U; e < (sizeof(table) / sizeof(table[0])); ++e) {
76 const size_t nlen = strlen(table[e].name);
77 if (((start + nlen) <= len) && (memcmp(s + start, table[e].name, nlen) == 0)) {
78 *i = start + nlen - 1U; /* leave *i on the ';'; the loop's ++ steps past */
79 *space = (table[e].ch == '\0');
80 *out_ch = table[e].ch;
81 return true;
82 }
83 }
84 return false;
85}
86
105RA8_INTERNAL static void
106internal_append_out_char(char* out, size_t* n, size_t cap, bool* fits, char ch)
107{
108 if ((*n + 1U) < cap) {
109 out[*n] = ch;
110 ++(*n);
111 } else {
112 *fits = false;
113 }
114}
115
135RA8_INTERNAL static bool
136internal_clean_inner_text(const char* text, size_t len, char* out, size_t cap)
137{
138 size_t n = 0U;
139 bool pending = false; /* a space is owed before the next visible char */
140 bool fits = true;
141 for (size_t i = 0U; i < len; ++i) {
142 const char c = text[i];
143 if (c == '<') {
144 while ((i < len) && (text[i] != '>')) {
145 ++i; /* skip a nested tag such as <span> */
146 }
147 continue;
148 }
149 char emit = c;
150 bool space = internal_is_ws(c);
151 if (c == '&') {
152 char decoded = '\0';
153 if (internal_decode_entity(text, len, &i, &decoded, &space)) {
154 emit = decoded;
155 }
156 }
157 if (space) {
158 pending = (n > 0U); /* collapse; never a leading space */
159 continue;
160 }
161 if (pending) {
162 internal_append_out_char(out, &n, cap, &fits, ' ');
163 }
164 pending = false;
165 internal_append_out_char(out, &n, cap, &fits, emit);
166 }
167 out[n] = '\0';
168 return fits;
169}
170
185RA8_INTERNAL static void internal_url_slug(const char* url, char* out, size_t cap)
186{
187 const char* q = strpbrk(url, "?#");
188 const size_t end = (q == nullptr) ? strlen(url) : (size_t)(q - url);
189 size_t beg = 0U;
190 size_t fin = end;
191 while ((fin > 0U) && (url[fin - 1U] == '/')) {
192 --fin; /* drop trailing slashes */
193 }
194 beg = fin;
195 while ((beg > 0U) && (url[beg - 1U] != '/')) {
196 --beg; /* walk back to the segment start */
197 }
198 size_t n = 0U;
199 for (size_t i = beg; (i < fin) && ((n + 1U) < cap); ++i) {
200 out[n] = url[i];
201 ++n;
202 }
203 out[n] = '\0';
204}
205
229RA8_INTERNAL static bool internal_anchor_title(const char* html,
230 size_t html_len,
231 const char* tag,
232 size_t tag_len,
233 size_t gt_off,
234 char* out,
235 size_t cap)
236{
237 if (internal_find_attr_value(tag, tag_len, "title", out, cap) && (out[0] != '\0')) {
238 return true;
239 }
240 if (gt_off >= html_len) {
241 return false;
242 }
243 const size_t text_off = gt_off + 1U;
244 const char* close = internal_find_ci(html + text_off, html_len - text_off, "</a");
245 const size_t text_len =
246 (close == nullptr) ? (html_len - text_off) : (size_t)(close - (html + text_off));
247 return internal_clean_inner_text(html + text_off, text_len, out, cap) && (out[0] != '\0');
248}
249
265RA8_INTERNAL static size_t internal_hit_index_of(const mdl_hit_list_t* out, const char* url)
266{
267 for (size_t i = 0U; i < out->count; ++i) {
268 if (strcmp(out->hits[i].url, url) == 0) {
269 return i;
270 }
271 }
272 return out->count;
273}
274
295 bool* real,
296 const char* url,
297 const char* title,
298 bool title_real)
299{
300 const size_t at = internal_hit_index_of(out, url);
301 if (at < out->count) {
302 if (title_real && !real[at]) {
303 (void)internal_copy_fits(out->hits[at].title, k_mdl_hit_title_max, title);
304 real[at] = true;
305 }
306 return k_ra8_ok;
307 }
308 if (out->count >= (size_t)k_mdl_max_hits) {
309 return k_ra8_err_no_mem;
310 }
311 (void)internal_copy_fits(out->hits[out->count].url, k_mdl_url_max, url);
312 (void)internal_copy_fits(out->hits[out->count].title, k_mdl_hit_title_max, title);
313 real[out->count] = title_real;
314 out->count++;
315 return k_ra8_ok;
316}
317
341 size_t html_len,
342 const char* base_url,
343 const char* keep,
344 const char* tag,
345 size_t tag_len,
346 size_t gt_off,
347 mdl_hit_list_t* out,
348 bool* real)
349{
350 char raw[k_mdl_url_max];
351 if (!internal_find_attr_value(tag, tag_len, "href", raw, sizeof(raw))) {
352 return k_ra8_ok; /* an <a> with no href is not a link */
353 }
354 char abs[k_mdl_url_max];
355 if (!mdl_extract_resolve_url(base_url, raw, abs, sizeof(abs))) {
356 return k_ra8_ok; /* unresolvable (e.g. javascript:, fragment): not counted
357 */
358 }
359 out->anchors_seen++;
360 if (!internal_contains_ok(abs, keep)) {
361 return k_ra8_ok; /* a real link, but not a result: counted, not stored */
362 }
363 char title[k_mdl_hit_title_max];
364 const bool title_real =
365 internal_anchor_title(html, html_len, tag, tag_len, gt_off, title, sizeof(title));
366 if (!title_real) {
367 internal_url_slug(abs, title, sizeof(title));
368 }
369 return internal_merge_hit(out, real, abs, title, title_real);
370}
371
373 size_t html_len,
374 const char* base_url,
375 const char* url_contains,
376 mdl_hit_list_t* out)
377{
378 if ((html == nullptr) || (base_url == nullptr) || (out == nullptr)) {
380 }
381 out->count = 0U;
382 out->anchors_seen = 0U;
383 bool real[k_mdl_max_hits];
384 memset(real, 0, sizeof(real));
385
386 size_t pos = 0U;
387 while (pos < html_len) {
388 const char* tag = internal_find_ci(html + pos, html_len - pos, "<a");
389 if (tag == nullptr) {
390 break;
391 }
392 const size_t tag_off = (size_t)(tag - html);
393 const size_t after = tag_off + (sizeof("<a") - 1U);
394 if ((after < html_len) && !internal_is_name_end(html[after])) {
395 pos = tag_off + 1U; /* "<article" while seeking "<a" */
396 continue;
397 }
398 const char* gt = memchr(tag, '>', html_len - tag_off);
399 const size_t gt_off = (gt == nullptr) ? html_len : (size_t)(gt - html);
400 const size_t tag_len = (gt == nullptr) ? (html_len - tag_off) : (size_t)(gt - tag);
401 if (internal_emit_hit(html,
402 html_len,
403 base_url,
404 url_contains,
405 tag,
406 tag_len,
407 gt_off,
408 out,
409 real) == k_ra8_err_no_mem) {
410 return k_ra8_err_no_mem;
411 }
412 pos = tag_off + ((gt == nullptr) ? tag_len : (tag_len + 1U));
413 }
414 return k_ra8_ok;
415}
416
433RA8_INTERNAL static bool internal_class_has_token(const char* classes, const char* value)
434{
435 const size_t value_len = strlen(value);
436 const char* at = classes;
437 while ((at = strstr(at, value)) != nullptr) {
438 const bool left_ok = (at == classes) || internal_is_ws(at[-1]);
439 const char after = at[value_len];
440 const bool right_ok = (after == '\0') || internal_is_ws(after);
441 if (left_ok && right_ok) {
442 return true;
443 }
444 at += value_len;
445 }
446 return false;
447}
448
466RA8_INTERNAL static ra8_err_t internal_selector_copy(const char* value, char* out, size_t cap)
467{
468 if (value[0] == '\0') {
469 return k_ra8_err_not_found;
470 }
471 if (!internal_copy_fits(out, cap, value)) {
473 }
474 return k_ra8_ok;
475}
476
498internal_extract_meta(const char* html, size_t len, const char* key, char* out, size_t cap)
499{
500 size_t pos = 0U;
501 while (pos < len) {
502 const char* tag = internal_find_ci(html + pos, len - pos, "<meta");
503 if (tag == nullptr) {
504 return k_ra8_err_not_found;
505 }
506 const size_t off = (size_t)(tag - html);
507 const char* gt = memchr(tag, '>', len - off);
508 if (gt == nullptr) {
509 return k_ra8_err_not_found;
510 }
511 const size_t tag_len = (size_t)(gt - tag);
512 char name[k_mdl_hit_title_max];
513 char content[k_mdl_url_max];
514 const bool named = internal_find_attr_value(tag, tag_len, "property", name, sizeof(name)) ||
515 internal_find_attr_value(tag, tag_len, "name", name, sizeof(name));
516 if (named && (strcmp(name, key) == 0) &&
517 internal_find_attr_value(tag, tag_len, "content", content, sizeof(content))) {
518 if (!internal_clean_inner_text(content, strlen(content), out, cap)) {
520 }
521 return (out[0] == '\0') ? k_ra8_err_not_found : k_ra8_ok;
522 }
523 pos = off + tag_len + 1U;
524 }
525 return k_ra8_err_not_found;
526}
527
549internal_extract_class(const char* html, size_t len, const char* token, char* out, size_t cap)
550{
551 size_t pos = 0U;
552 while (pos < len) {
553 const char* tag = memchr(html + pos, '<', len - pos);
554 if (tag == nullptr) {
555 return k_ra8_err_not_found;
556 }
557 const size_t off = (size_t)(tag - html);
558 const char* gt = memchr(tag, '>', len - off);
559 if (gt == nullptr) {
560 return k_ra8_err_not_found;
561 }
562 const size_t tag_len = (size_t)(gt - tag);
563 char classes[k_mdl_hit_title_max];
564 if (internal_find_attr_value(tag, tag_len, "class", classes, sizeof(classes)) &&
565 internal_class_has_token(classes, token)) {
566 const size_t inner_off = (size_t)(gt - html) + 1U;
567 const char* close = internal_find_ci(html + inner_off, len - inner_off, "</");
568 const size_t inner_len =
569 (close == nullptr) ? (len - inner_off) : (size_t)(close - (html + inner_off));
570 if (!internal_clean_inner_text(html + inner_off, inner_len, out, cap)) {
572 }
573 return (out[0] == '\0') ? k_ra8_err_not_found : k_ra8_ok;
574 }
575 pos = off + tag_len + 1U;
576 }
577 return k_ra8_err_not_found;
578}
579
601internal_extract_label(const char* html, size_t len, const char* label, char* out, size_t cap)
602{
603 const char* hit = internal_find_ci(html, len, label);
604 if (hit == nullptr) {
605 return k_ra8_err_not_found;
606 }
607 const size_t after_label = (size_t)(hit - html) + strlen(label);
608 const char* anchor = internal_find_ci(html + after_label, len - after_label, "<a");
609 if (anchor == nullptr) {
610 return k_ra8_err_not_found;
611 }
612 const size_t anchor_off = (size_t)(anchor - html);
613 const char* gt = memchr(anchor, '>', len - anchor_off);
614 if (gt == nullptr) {
615 return k_ra8_err_not_found;
616 }
617 const size_t inner_off = (size_t)(gt - html) + 1U;
618 const char* close = internal_find_ci(html + inner_off, len - inner_off, "</a");
619 const size_t inner_len =
620 (close == nullptr) ? (len - inner_off) : (size_t)(close - (html + inner_off));
621 if (!internal_clean_inner_text(html + inner_off, inner_len, out, cap)) {
623 }
624 return (out[0] == '\0') ? k_ra8_err_not_found : k_ra8_ok;
625}
626
628 size_t html_len,
629 const char* selector,
630 char* out,
631 size_t out_cap)
632{
633 if ((html == nullptr) || (selector == nullptr) || (out == nullptr) || (out_cap == 0U)) {
635 }
636 out[0] = '\0';
637 const char* value = strchr(selector, ':');
638 if ((value == nullptr) || (value[1] == '\0')) {
640 }
641 ++value;
642 if (strncmp(selector, "meta:", sizeof("meta:") - 1U) == 0) {
643 return internal_extract_meta(html, html_len, value, out, out_cap);
644 }
645 if (strncmp(selector, "class:", sizeof("class:") - 1U) == 0) {
646 return internal_extract_class(html, html_len, value, out, out_cap);
647 }
648 if (strncmp(selector, "label:", sizeof("label:") - 1U) == 0) {
649 return internal_extract_label(html, html_len, value, out, out_cap);
650 }
651 if (strncmp(selector, "literal:", sizeof("literal:") - 1U) == 0) {
652 return internal_selector_copy(value, out, out_cap);
653 }
655}
Extract image URLs and anchor links from an HTML page (v1 scanner).
@ k_mdl_max_hits
Max titled hits captured per results page.
Definition mdl_extract.h:36
@ k_mdl_hit_title_max
Max title bytes per hit, including the NUL.
Definition mdl_extract.h:37
@ k_mdl_url_max
Max bytes per URL, including the NUL.
Definition mdl_extract.h:25
bool mdl_extract_resolve_url(const char *base, const char *raw, char *out, size_t out_cap)
Resolve a possibly relative HTTP(S) URL against an absolute base URL.
static void internal_append_out_char(char *out, size_t *n, size_t cap, bool *fits, char ch)
Append one character to bounded output, tracking overflow.
static ra8_err_t internal_extract_class(const char *html, size_t len, const char *token, char *out, size_t cap)
Extract visible text from the first matching class token.
static ra8_err_t internal_extract_meta(const char *html, size_t len, const char *key, char *out, size_t cap)
Extract one matching HTML meta-content value.
static ra8_err_t internal_emit_hit(const char *html, size_t html_len, const char *base_url, const char *keep, const char *tag, size_t tag_len, size_t gt_off, mdl_hit_list_t *out, bool *real)
Resolve one anchor's href, count it, and emit a filtered hit.
static bool internal_decode_entity(const char *s, size_t len, size_t *i, char *out_ch, bool *space)
Decode an entity at &, returning its byte and advancing *i.
static bool internal_is_ws(char c)
ASCII whitespace test (locale-independent).
static size_t internal_hit_index_of(const mdl_hit_list_t *out, const char *url)
Index of an existing hit with URL url, or count when absent.
static ra8_err_t internal_merge_hit(mdl_hit_list_t *out, bool *real, const char *url, const char *title, bool title_real)
Merge one resolved (url,title) hit; upgrade a slug with a real title.
static void internal_url_slug(const char *url, char *out, size_t cap)
Fill out with the URL's last non-empty path segment (slug fallback).
static bool internal_class_has_token(const char *classes, const char *value)
Find one whitespace-delimited class token.
static bool internal_clean_inner_text(const char *text, size_t len, char *out, size_t cap)
Copy the anchor inner text from text into out, cleaned for display.
static ra8_err_t internal_selector_copy(const char *value, char *out, size_t cap)
Copy one non-empty literal selector value.
ra8_err_t mdl_extract_hits(const char *html, size_t html_len, const char *base_url, const char *url_contains, mdl_hit_list_t *out)
Scan html for <a href> links, keeping each hit's title and URL.
static ra8_err_t internal_extract_label(const char *html, size_t len, const char *label, char *out, size_t cap)
Extract the first anchor text following a visible label.
ra8_err_t mdl_extract_selector(const char *html, size_t html_len, const char *selector, char *out, size_t out_cap)
Extract one bounded metadata value using a site-descriptor selector.
static bool internal_anchor_title(const char *html, size_t html_len, const char *tag, size_t tag_len, size_t gt_off, char *out, size_t cap)
Read the title for the anchor whose > sits at gt_off.
Shared bounded markup lexer helpers for media extraction units.
static bool internal_find_attr_value(const char *tag, size_t tag_len, const char *attr, char *out, size_t out_cap)
Read the value of attr inside one tag [tag, tag+tag_len).
static bool internal_copy_fits(char *out, size_t out_cap, const char *src)
Copy src into out if it fits (incl.
static const char * internal_find_ci(const char *hay, size_t hay_len, const char *needle)
Case-insensitive search for needle in [hay, hay+hay_len).
static bool internal_contains_ok(const char *url, const char *needle)
True if needle is empty/NULL or a substring of url.
static bool internal_is_name_end(char c)
True if c terminates a tag name (so "<a" != "<article").
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
int strncmp(const char *s1, const char *s2, size_t n)
Compare two strings up to a specified length.
void * memchr(const void *s, int c, size_t n)
Locate a byte in a memory area.
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
int memcmp(const void *a, const void *b, size_t n)
Compare bytes in two memory areas.
size_t strlen(const char *s)
Calculate string length.
char * strstr(const char *haystack, const char *needle)
Locate substring in string.
char * strchr(const char *s, int c)
Locate first occurrence of character in string.
int abs(int j)
Compute absolute value of integer.
One recognised HTML entity mapped to its single replacement byte.
const char * name
Entity body after '&', including the ';'.
char ch
Decoded byte ('\0' means "emit a space").
Bounded list of titled hits plus the raw anchor tally.
Definition mdl_extract.h:68
mdl_hit_t hits[k_mdl_max_hits]
Deduplicated titled hits.
Definition mdl_extract.h:69
size_t count
Number of valid hits.
Definition mdl_extract.h:70
size_t anchors_seen
Total resolvable <a href> anchors.
Definition mdl_extract.h:71
char title[k_mdl_hit_title_max]
Display title, NUL-terminated.
Definition mdl_extract.h:51
char url[k_mdl_url_max]
Absolute series URL, NUL-terminated.
Definition mdl_extract.h:52