ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
book_xhtml.c
Go to the documentation of this file.
1
23#include <string.h>
24
25#include "book.h"
26#include "book_internal.h"
27#include "book_paged.h"
28#include "ra8_attributes.h"
29#include "ra8_check.h"
30
32static const char* const s_tag_xhtml = "book_xhtml";
33
40typedef struct {
41 bool is_close;
42 uint32_t value;
44
74RA8_INTERNAL static bool
75internal_emit(char* out, size_t cap, size_t* pos, const char* src, size_t len)
76{
77 if (len > (cap - *pos)) {
78 return false;
79 }
80 (void)memcpy(&out[*pos], src, len);
81 *pos += len;
82 return true;
83}
84
113RA8_INTERNAL static bool internal_emit_cstr(char* out, size_t cap, size_t* pos, const char* str)
114{
115 return internal_emit(out, cap, pos, str, strlen(str));
116}
117
157RA8_INTERNAL static bool
158internal_emit_escaped(char* out, size_t cap, size_t* pos, const char* str, bool in_attr)
159{
160 for (const char* p = str; *p != '\0'; ++p) {
161 bool ok = true;
162 switch (*p) {
163 case '&':
164 ok = internal_emit_cstr(out, cap, pos, "&amp;");
165 break;
166 case '<':
167 ok = internal_emit_cstr(out, cap, pos, "&lt;");
168 break;
169 case '>':
170 ok = internal_emit_cstr(out, cap, pos, "&gt;");
171 break;
172 case '"':
173 ok = in_attr ? internal_emit_cstr(out, cap, pos, "&quot;")
174 : internal_emit(out, cap, pos, p, 1U);
175 break;
176 default:
177 ok = internal_emit(out, cap, pos, p, 1U);
178 break;
179 }
180 if (!ok) {
181 return false;
182 }
183 }
184 return true;
185}
186
214RA8_INTERNAL static bool internal_is_void(const char* name)
215{
216 static const char* const k_void[] = {
217 "br",
218 "hr",
219 "img",
220 "meta",
221 "link",
222 "input",
223 "area",
224 "base",
225 "col",
226 "embed",
227 "param",
228 "source",
229 "track",
230 "wbr",
231 };
232 for (size_t i = 0U; i < (sizeof(k_void) / sizeof(k_void[0])); ++i) {
233 if (strcmp(name, k_void[i]) == 0) {
234 return true;
235 }
236 }
237 return false;
238}
239
273RA8_INTERNAL static bool
274internal_emit_attrs(const void* base, const book_node_t* node, char* out, size_t cap, size_t* pos)
275{
276 const book_attr_t* attrs = book_attrs(base);
277 for (uint16_t i = 0U; i < node->attr_count; ++i) {
278 const book_attr_t* a = &attrs[node->first_attr + i];
279 bool ok = internal_emit(out, cap, pos, " ", 1U) &&
280 internal_emit_cstr(out, cap, pos, book_string(base, a->name_off)) &&
281 internal_emit(out, cap, pos, "=\"", 2U) &&
282 internal_emit_escaped(out, cap, pos, book_string(base, a->value_off), true) &&
283 internal_emit(out, cap, pos, "\"", 1U);
284 if (!ok) {
285 return false;
286 }
287 }
288 return true;
289}
290
331RA8_INTERNAL static bool internal_open_element(const void* base,
332 const book_node_t* node,
333 char* out,
334 size_t cap,
335 size_t* pos,
336 book_walk_entry_t* stack,
337 uint32_t* sp)
338{
339 const char* name = book_string(base, node->name_off);
340 if (!internal_emit(out, cap, pos, "<", 1U) || !internal_emit_cstr(out, cap, pos, name) ||
341 !internal_emit_attrs(base, node, out, cap, pos)) {
342 return false;
343 }
344 if (internal_is_void(name)) {
345 return internal_emit(out, cap, pos, "/>", 2U);
346 }
347 if (!internal_emit(out, cap, pos, ">", 1U) || ((*sp + 2U) > k_book_xhtml_stack)) {
348 return false;
349 }
350 stack[*sp] = (book_walk_entry_t){true, node->name_off}; /* close after children */
351 *sp += 1U;
352 stack[*sp] = (book_walk_entry_t){false, node->first_child}; /* children first */
353 *sp += 1U;
354 return true;
355}
356
393RA8_INTERNAL static bool internal_walk_to_xhtml(const void* base,
394 uint32_t root,
395 uint32_t node_count,
396 char* out,
397 size_t cap,
398 size_t* pos)
399{
400 const book_node_t* nodes = book_nodes(base);
401 /* Explicit DFS stack (~4 KiB) kept in module-static storage so this frame
402 * stays within the stack-usage budget; the walk is iterative (no recursion)
403 * and single-threaded, so the shared buffer never overlaps. */
404 static book_walk_entry_t s_xhtml_stack[k_book_xhtml_stack];
405 book_walk_entry_t* stack = s_xhtml_stack;
406 uint32_t sp = 0U;
407 bool ok = true;
408 stack[sp] = (book_walk_entry_t){false, root};
409 sp += 1U;
410
411 const uint32_t max_iter = (node_count * k_book_xhtml_iter_x) + k_book_xhtml_stack;
412 uint32_t guard = 0U;
413 while ((sp > 0U) && ok && (guard < max_iter)) {
414 ++guard;
415 sp -= 1U;
416 book_walk_entry_t e = stack[sp];
417 if (e.is_close) {
418 ok = internal_emit(out, cap, pos, "</", 2U) &&
419 internal_emit_cstr(out, cap, pos, book_string(base, e.value)) &&
420 internal_emit(out, cap, pos, ">", 1U);
421 continue;
422 }
423 if (e.value == k_book_nil) {
424 continue;
425 }
426 const book_node_t* node = &nodes[e.value];
427 if (sp >= k_book_xhtml_stack) {
428 return false;
429 }
430 /* Continue this level's sibling chain after the whole subtree + close. */
431 stack[sp] = (book_walk_entry_t){false, node->next_sibling};
432 sp += 1U;
433 if (node->kind == (uint8_t)k_book_node_text) {
434 ok = internal_emit_escaped(out, cap, pos, book_string(base, node->text_off), false);
435 } else {
436 ok = internal_open_element(base, node, out, cap, pos, stack, &sp);
437 }
438 }
439 return ok && (guard < max_iter);
440}
441
470RA8_PRIV bool priv_book_is_block(const char* name)
471{
472 static const char* const k_block[] = {
473 "p", "h1", "h2", "h3", "h4", "h5", "h6", "li",
474 "ul", "ol", "div", "br", "hr", "section", "tr", "pre",
475 "header", "blockquote", "article", "aside", "footer", "figure", "figcaption",
476 };
477 for (size_t i = 0U; i < (sizeof(k_block) / sizeof(k_block[0])); ++i) {
478 if (strcmp(name, k_block[i]) == 0) {
479 return true;
480 }
481 }
482 return false;
483}
484
525RA8_PRIV bool
526priv_book_emit_text(char* out, size_t cap, size_t* pos, const char* str, bool* at_break)
527{
528 for (const char* p = str; *p != '\0'; ++p) {
529 const char c = *p;
530 const bool ws = (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
531 if (!ws) {
532 if (!internal_emit(out, cap, pos, &c, 1U)) {
533 return false;
534 }
535 *at_break = false;
536 continue;
537 }
538 if (*at_break) {
539 continue;
540 }
541 if (!internal_emit(out, cap, pos, " ", 1U)) {
542 return false;
543 }
544 *at_break = true;
545 }
546 return true;
547}
548
583RA8_PRIV bool priv_book_emit_break(char* out, size_t cap, size_t* pos, bool* at_break)
584{
585 while ((*pos > 0U) && (out[*pos - 1U] == ' ')) {
586 (*pos)--;
587 }
588 *at_break = true;
589 if ((*pos > 0U) && (out[*pos - 1U] == '\n')) {
590 return true;
591 }
592 return internal_emit(out, cap, pos, "\n", 1U);
593}
594
633RA8_INTERNAL static bool internal_walk_text(const void* base,
634 uint32_t root,
635 uint32_t node_count,
636 char* out,
637 size_t cap,
638 size_t* pos)
639{
640 const book_node_t* nodes = book_nodes(base);
641 /* Explicit DFS stack (2 KiB) kept in module-static storage so this frame
642 * stays within the stack-usage budget; iterative (no recursion) and
643 * single-threaded, so the shared buffer never overlaps. */
644 static uint32_t s_text_stack[k_book_xhtml_stack];
645 uint32_t* stack = s_text_stack;
646 uint32_t sp = 0U;
647 bool ok = true;
648 bool at_break = true;
649 stack[sp] = root;
650 sp += 1U;
651
652 const uint32_t max_iter = (node_count * k_book_xhtml_iter_x) + k_book_xhtml_stack;
653 uint32_t guard = 0U;
654 while ((sp > 0U) && ok && (guard < max_iter)) {
655 ++guard;
656 sp -= 1U;
657 const uint32_t n = stack[sp];
658 if (n == k_book_nil) {
659 continue;
660 }
661 const book_node_t* node = &nodes[n];
662 if (sp >= k_book_xhtml_stack) {
663 return false;
664 }
665 stack[sp] = node->next_sibling; /* sibling chain after this subtree */
666 sp += 1U;
667 if (node->kind == (uint8_t)k_book_node_text) {
668 ok = priv_book_emit_text(out, cap, pos, book_string(base, node->text_off), &at_break);
669 continue;
670 }
671 if (priv_book_is_block(book_string(base, node->name_off))) {
672 ok = priv_book_emit_break(out, cap, pos, &at_break);
673 }
674 if (ok && (sp < k_book_xhtml_stack)) {
675 stack[sp] = node->first_child; /* descend, pre-order */
676 sp += 1U;
677 }
678 }
679 return ok && (guard < max_iter);
680}
681
683book_chapter_text(const void* base, uint32_t chapter_idx, char* out, size_t cap, size_t* out_len)
684{
685 RA8_CHECK_NULL_PTR(base, s_tag_xhtml, "text: null base");
686 RA8_CHECK_NULL_PTR(out, s_tag_xhtml, "text: null out");
687 RA8_CHECK_NULL_PTR(out_len, s_tag_xhtml, "text: null out_len");
688
689 const book_header_t* hdr = book_header(base);
690 if (chapter_idx >= hdr->chapter_count) {
692 }
693 const uint32_t root = book_chapters(base)[chapter_idx].root_node;
694 size_t pos = 0U;
695 if (!internal_walk_text(base, root, hdr->node_count, out, cap, &pos)) {
697 }
698 *out_len = pos;
699 return k_ra8_ok;
700}
701
703 uint32_t chapter_idx,
704 char* out,
705 size_t cap,
706 size_t* out_len)
707{
708 RA8_CHECK_NULL_PTR(base, s_tag_xhtml, "to_xhtml: null base");
709 RA8_CHECK_NULL_PTR(out, s_tag_xhtml, "to_xhtml: null out");
710 RA8_CHECK_NULL_PTR(out_len, s_tag_xhtml, "to_xhtml: null out_len");
711
712 const book_header_t* hdr = book_header(base);
713 if (chapter_idx >= hdr->chapter_count) {
715 }
716
717 const uint32_t root = book_chapters(base)[chapter_idx].root_node;
718 size_t pos = 0U;
719 if (!internal_walk_to_xhtml(base, root, hdr->node_count, out, cap, &pos)) {
721 }
722 *out_len = pos;
723 return k_ra8_ok;
724}
Flat, execute-in-place container for a build-time "compiled" e-book.
@ k_book_node_text
A text run: carries a string, no children.
Definition book.h:163
@ k_book_nil
Absent index / "applies to all chapters".
Definition book.h:129
static const book_chapter_t * book_chapters(const void *base)
Base of the chapter table.
Definition book.h:458
static const book_header_t * book_header(const void *base)
View the blob base as its header.
Definition book.h:382
static const book_node_t * book_nodes(const void *base)
Base of the DOM node table.
Definition book.h:473
static const book_attr_t * book_attrs(const void *base)
Base of the attribute table.
Definition book.h:488
static const char * book_string(const void *base, uint32_t off)
Resolve a string-pool offset to a NUL-terminated UTF-8 string.
Definition book.h:443
book DOM-walk helpers shared across the library's translation units.
@ k_book_xhtml_stack
Max pending open/close walk entries.
@ k_book_xhtml_iter_x
Iteration-guard multiplier over node_count.
Paged (demand-fetched) accessor mode for book over ra8_vmem (#163).
ra8_err_t book_chapter_text(const void *base, uint32_t chapter_idx, char *out, size_t cap, size_t *out_len)
Extract one chapter's readable plain text from the DOM.
Definition book_xhtml.c:683
bool priv_book_is_block(const char *name)
Test whether an element name is a block-level HTML element.
Definition book_xhtml.c:470
static bool internal_emit_cstr(char *out, size_t cap, size_t *pos, const char *str)
Append a NUL-terminated string to the output buffer.
Definition book_xhtml.c:113
static bool internal_emit_attrs(const void *base, const book_node_t *node, char *out, size_t cap, size_t *pos)
Append all attributes of a DOM node to the output buffer.
Definition book_xhtml.c:274
static const char *const s_tag_xhtml
Log tag for serializer diagnostics.
Definition book_xhtml.c:32
bool priv_book_emit_break(char *out, size_t cap, size_t *pos, bool *at_break)
Append a paragraph break, collapsing consecutive block-level breaks.
Definition book_xhtml.c:583
static bool internal_open_element(const void *base, const book_node_t *node, char *out, size_t cap, size_t *pos, book_walk_entry_t *stack, uint32_t *sp)
Emit an element open tag and schedule its close and children on the walk stack.
Definition book_xhtml.c:331
ra8_err_t book_chapter_to_xhtml(const void *base, uint32_t chapter_idx, char *out, size_t cap, size_t *out_len)
Serialize one chapter's DOM subtree back to XHTML for the renderer.
Definition book_xhtml.c:702
bool priv_book_emit_text(char *out, size_t cap, size_t *pos, const char *str, bool *at_break)
Append a whitespace-collapsed text run to the output buffer.
Definition book_xhtml.c:526
static bool internal_is_void(const char *name)
Test whether an element name is an HTML void element.
Definition book_xhtml.c:214
static bool internal_walk_text(const void *base, uint32_t root, uint32_t node_count, char *out, size_t cap, size_t *pos)
Bounded pre-order walk that extracts plain text from a DOM subtree.
Definition book_xhtml.c:633
static bool internal_emit(char *out, size_t cap, size_t *pos, const char *src, size_t len)
Append a raw byte span to the output buffer.
Definition book_xhtml.c:75
static bool internal_emit_escaped(char *out, size_t cap, size_t *pos, const char *str, bool in_attr)
Append entity-escaped text to the output buffer.
Definition book_xhtml.c:158
static bool internal_walk_to_xhtml(const void *base, uint32_t root, uint32_t node_count, char *out, size_t cap, size_t *pos)
Iterative, bounded DOM walk that serialises a subtree to XHTML.
Definition book_xhtml.c:393
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
@ 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_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 strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
size_t strlen(const char *s)
Calculate string length.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
One name="value" attribute on an element.
Definition book.h:317
uint32_t value_off
String-pool offset of the attribute value.
Definition book.h:319
uint32_t name_off
String-pool offset of the attribute name.
Definition book.h:318
uint32_t root_node
Node-table index of this chapter's root element.
Definition book.h:283
Fixed 100-byte prologue describing every table and pool in the blob.
Definition book.h:246
uint32_t node_count
Number of DOM nodes.
Definition book.h:258
uint32_t chapter_count
Number of spine chapters.
Definition book.h:256
One DOM node.
Definition book.h:299
uint8_t kind
book_node_kind_t.
Definition book.h:300
uint32_t text_off
Text: string-pool offset of the run (element: 0).
Definition book.h:304
uint16_t attr_count
Element: number of attributes (text: 0).
Definition book.h:302
uint32_t next_sibling
Index of next sibling node, or nil.
Definition book.h:307
uint32_t name_off
Element: string-pool offset of tag name (text: 0).
Definition book.h:303
uint32_t first_child
Index of first child node, or nil.
Definition book.h:306
uint32_t first_attr
Index of first attribute, or nil.
Definition book.h:305
One deferred unit of work on the serializer stack.
Definition book_xhtml.c:40
bool is_close
true: emit </name>; false: open node value.
Definition book_xhtml.c:41
uint32_t value
Close: tag-name string offset.
Definition book_xhtml.c:42