ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
epub_xml_shim.c
Go to the documentation of this file.
1
15#include <stddef.h>
16#include <stdint.h>
17#include <string.h>
18
22#include "ra8_attributes.h"
23#include "xml.h"
24
34
35void priv_epub_xml_copy(const uint8_t* source,
36 size_t source_len,
37 xml_span_t span,
38 char* destination,
39 size_t capacity)
40{
41 size_t ignored = 0U;
42 (void)xml_decode_prefix(source, source_len, span, destination, capacity, &ignored);
43}
44
45priv_attr_t priv_epub_xml_attr(const uint8_t* source,
46 size_t source_len,
47 const xml_event_t* event,
48 const char* name)
49{
50 xml_attr_cursor_t cursor = {};
51 xml_attr_begin(event, &cursor);
52 for (uint16_t i = 0U; i < event->attribute_count; ++i) {
53 xml_attribute_t attribute = {};
54 bool present = false;
55 // mcdc-deactivated: priv_epub_xml_attr attribute re-walk gate; internal_attributes fixed event->attribute_count by successfully parsing exactly that many attributes over these immutable bytes, so the bounded re-parse cannot fail and xml_attr_next() only clears `present` once `emitted` reaches that same count, which this loop never exceeds -- both conditions are constant-false.
56 if ((xml_attr_next(source, source_len, event, &cursor, &attribute, &present) != k_ra8_ok) ||
57 !present) {
58 break;
59 }
60 if (xml_span_equal(source, source_len, attribute.name, name)) {
61 return (priv_attr_t){attribute.value, true};
62 }
63 }
64 return (priv_attr_t){};
65}
66
67bool priv_epub_xml_attr_contains(const uint8_t* source,
68 size_t source_len,
69 priv_attr_t attribute,
70 const char* needle)
71{
72 if (!attribute.present) {
73 return false;
74 }
75 char value[k_epub_max_path_len] = {};
76 size_t length = 0U;
77 if (xml_decode(source, source_len, attribute.span, value, sizeof(value), &length) != k_ra8_ok) {
78 return false;
79 }
80 return strstr(value, needle) != nullptr;
81}
82
83xml_span_t priv_epub_xml_frame_name(const xml_reader_t* reader, uint16_t frame)
84{
85 const xml_frame_t* value = &reader->workspace->frames[frame];
86 return (xml_span_t){value->name_offset, value->name_length};
87}
88
89bool priv_epub_xml_direct_child(uint16_t child, uint16_t parent)
90{
91 const uint16_t parent_depth = parent;
92 if (parent_depth == UINT16_MAX) {
93 return false;
94 }
95 return child == (uint16_t)(parent_depth + 1U);
96}
97
98bool priv_epub_xml_ancestor_frame(uint16_t depth, uint16_t levels, uint16_t* out_frame)
99{
100 uint16_t frame = depth;
101 uint16_t remaining = levels;
102 while (remaining > 0U) {
103 if (frame == 0U) {
104 return false;
105 }
106 --frame;
107 --remaining;
108 }
109 *out_frame = frame;
110 return true;
111}
112
113uint16_t priv_epub_xml_ancestor_marker(const xml_reader_t* reader, uint16_t depth, uint16_t levels)
114{
115 uint16_t frame = 0U;
116 return priv_epub_xml_ancestor_frame(depth, levels, &frame)
117 ? reader->workspace->frames[frame].consumer
118 : 0U;
119}
120
122 const uint8_t* source,
123 size_t length,
124 epub_xml_workspace_t* workspace)
125{
126 return xml_reader_init(reader, source, length, &workspace->reader);
127}
128
129ra8_err_t priv_epub_xml_find(const uint8_t* source,
130 size_t length,
131 epub_xml_workspace_t* workspace,
132 const char* local,
133 bool use_parent,
134 uint32_t parent_offset,
135 uint16_t parent_depth,
136 xml_event_t* out)
137{
138 xml_reader_t reader = {};
139 ra8_err_t err = priv_epub_xml_reader(&reader, source, length, workspace);
140 bool parent_active = false;
141 while (err == k_ra8_ok) {
142 xml_event_t event = {};
143 err = xml_reader_next(&reader, &event);
144 if ((err != k_ra8_ok) || (event.kind == (uint8_t)k_xml_event_none)) {
145 break;
146 }
147 if (use_parent && (event.kind == (uint8_t)k_xml_event_start) && (event.depth == parent_depth) &&
148 (event.markup.offset == parent_offset)) {
149 parent_active = true;
150 continue;
151 }
152 if ((event.kind == (uint8_t)k_xml_event_start) &&
153 xml_span_local_equal(source, length, event.name, local)) {
154 if (!use_parent || (parent_active && priv_epub_xml_direct_child(event.depth, parent_depth))) {
155 *out = event;
156 return k_ra8_ok;
157 }
158 } else if (use_parent && parent_active && (event.kind == (uint8_t)k_xml_event_end) &&
159 (event.depth == parent_depth)) {
161 } else {
162 /* This event does not affect the requested element search. */
163 }
164 }
165 return (err == k_ra8_ok) ? k_ra8_err_validation_failed : err;
166}
167
169 size_t xml_len,
171 epub_xml_workspace_t* workspace)
172{
173 if ((xml_bytes == nullptr) || (out == nullptr) || (workspace == nullptr)) {
174 return k_ra8_err_null_ptr;
175 }
176 if (xml_len == 0U) {
178 }
179 ra8_err_t err = xml_validate(xml_bytes, xml_len, &workspace->reader);
180 xml_event_t rootfiles = {};
181 if (err == k_ra8_ok) {
182 err = priv_epub_xml_find(xml_bytes, xml_len, workspace, "rootfiles", false, 0U, 0U, &rootfiles);
183 }
184 xml_event_t rootfile = {};
185 if (err == k_ra8_ok) {
186 err = priv_epub_xml_find(xml_bytes,
187 xml_len,
188 workspace,
189 "rootfile",
190 true,
191 rootfiles.markup.offset,
192 rootfiles.depth,
193 &rootfile);
194 } else if (err == k_ra8_err_validation_failed) {
195 err = priv_epub_xml_find(xml_bytes, xml_len, workspace, "rootfile", false, 0U, 0U, &rootfile);
196 } else {
197 /* Preserve the parser error for the common return below. */
198 }
199 if (err != k_ra8_ok) {
200 return err;
201 }
202 const priv_attr_t path = priv_epub_xml_attr(xml_bytes, xml_len, &rootfile, "full-path");
203 if (!path.present || (path.span.length == 0U)) {
205 }
206 out->opf_path[0] = '\0';
207 priv_epub_xml_copy(xml_bytes, xml_len, path.span, out->opf_path, sizeof(out->opf_path));
208 return k_ra8_ok;
209}
210
211RA8_INTERNAL static bool
212internal_font_type(const uint8_t* source, size_t source_len, priv_attr_t media)
213{
214 static const char* const types[] = {"application/font-sfnt",
215 "application/vnd.ms-opentype",
216 "font/ttf",
217 "font/otf",
218 "application/x-font-ttf"};
219 for (size_t i = 0U; i < (sizeof(types) / sizeof(types[0])); ++i) {
220 if (media.present && xml_span_equal(source, source_len, media.span, types[i])) {
221 return true;
222 }
223 }
224 return false;
225}
226
227RA8_INTERNAL static void internal_manifest_item(const uint8_t* source,
228 size_t source_len,
229 const xml_event_t* event,
230 epub_book_t* book)
231{
232 const priv_attr_t id = priv_epub_xml_attr(source, source_len, event, "id");
233 const priv_attr_t href = priv_epub_xml_attr(source, source_len, event, "href");
234 const priv_attr_t media = priv_epub_xml_attr(source, source_len, event, "media-type");
235 const priv_attr_t properties = priv_epub_xml_attr(source, source_len, event, "properties");
236 if (book->manifest_count < (uint16_t)k_epub_max_manifest) {
237 epub_manifest_item_t* item = &book->manifest[book->manifest_count];
238 ++book->manifest_count;
239 if (id.present) {
240 priv_epub_xml_copy(source, source_len, id.span, item->id, sizeof(item->id));
241 }
242 if (href.present) {
243 priv_epub_xml_copy(source, source_len, href.span, item->href, sizeof(item->href));
244 }
245 if (media.present) {
246 priv_epub_xml_copy(source,
247 source_len,
248 media.span,
249 item->media_type,
250 sizeof(item->media_type));
251 }
252 }
253 if (href.present && internal_font_type(source, source_len, media) &&
254 (book->embedded_font_count < (uint16_t)k_epub_max_fonts)) {
255 priv_epub_xml_copy(source,
256 source_len,
257 href.span,
260 }
261 if (href.present && priv_epub_xml_attr_contains(source, source_len, properties, "cover-image")) {
262 priv_epub_xml_copy(source, source_len, href.span, book->cover_path, sizeof(book->cover_path));
263 }
264 if (href.present && priv_epub_xml_attr_contains(source, source_len, properties, "nav")) {
265 priv_epub_xml_copy(source, source_len, href.span, book->toc_path, sizeof(book->toc_path));
266 book->toc_kind = (uint8_t)k_epub_toc_nav;
267 }
268}
269
270RA8_INTERNAL static void internal_metadata_text(const uint8_t* source,
271 size_t source_len,
272 const xml_event_t* event,
273 const xml_reader_t* reader,
274 epub_book_t* book)
275{
276 if (event->depth == 0U) {
277 return;
278 }
279 const uint16_t marker = priv_epub_xml_ancestor_marker(reader, event->depth, 1U);
280 if (marker == (uint16_t)k_priv_mark_title) {
281 priv_epub_xml_copy(source, source_len, event->markup, book->title, sizeof(book->title));
282 } else if (marker == (uint16_t)k_priv_mark_creator) {
283 priv_epub_xml_copy(source, source_len, event->markup, book->author, sizeof(book->author));
284 } else if (marker == (uint16_t)k_priv_mark_language) {
285 priv_epub_xml_copy(source, source_len, event->markup, book->language, sizeof(book->language));
286 } else if ((marker == (uint16_t)k_priv_mark_identifier_match) ||
287 ((marker == (uint16_t)k_priv_mark_identifier) && (book->identifier[0] == '\0'))) {
288 priv_epub_xml_copy(source,
289 source_len,
290 event->markup,
291 book->identifier,
292 sizeof(book->identifier));
293 } else {
294 /* Text outside recognized metadata elements is ignored. */
295 }
296}
297
298RA8_INTERNAL static void internal_mark_metadata(const uint8_t* source,
299 size_t source_len,
300 const xml_event_t* event,
301 xml_reader_t* reader,
302 xml_span_t unique_id)
303{
304 uint16_t marker = (uint16_t)k_priv_mark_none;
305 if (xml_span_local_equal(source, source_len, event->name, "title")) {
306 marker = (uint16_t)k_priv_mark_title;
307 } else if (xml_span_local_equal(source, source_len, event->name, "creator")) {
308 marker = (uint16_t)k_priv_mark_creator;
309 } else if (xml_span_local_equal(source, source_len, event->name, "language")) {
310 marker = (uint16_t)k_priv_mark_language;
311 } else if (xml_span_local_equal(source, source_len, event->name, "identifier")) {
312 marker = (uint16_t)k_priv_mark_identifier;
313 const priv_attr_t id = priv_epub_xml_attr(source, source_len, event, "id");
314 if (id.present && (unique_id.length > 0U) &&
315 xml_decoded_equal(source, source_len, id.span, unique_id)) {
316 marker = (uint16_t)k_priv_mark_identifier_match;
317 }
318 } else {
319 /* Unrecognized metadata elements retain the no-marker value. */
320 }
321 reader->workspace->frames[event->depth].consumer = marker;
322}
323
325internal_opf_status(ra8_err_t err, uint16_t manifest_depth, uint16_t spine_depth)
326{
327 // mcdc-deactivated: internal_opf_status package-shape gate; it runs only after internal_opf_shape accepted the same bytes using the identical depth-1 <manifest>/<spine> predicates that assign manifest_depth and spine_depth here, so neither can still be UINT16_MAX, and the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here -- all three conditions are constant.
328 return ((err == k_ra8_ok) && ((manifest_depth == UINT16_MAX) || (spine_depth == UINT16_MAX)))
330 : err;
331}
332
352RA8_INTERNAL static void internal_opf_metadata_child(const uint8_t* source,
353 size_t length,
354 const xml_event_t* event,
355 xml_reader_t* reader,
356 xml_span_t unique_id,
357 epub_book_t* book)
358{
359 internal_mark_metadata(source, length, event, reader, unique_id);
360 if (!xml_span_local_equal(source, length, event->name, "meta")) {
361 return;
362 }
363 const priv_attr_t name = priv_epub_xml_attr(source, length, event, "name");
364 const priv_attr_t content = priv_epub_xml_attr(source, length, event, "content");
365 if (name.present && content.present && xml_span_equal(source, length, name.span, "cover")) {
366 book->xml_workspace.legacy_cover_id = content.span;
367 }
368}
369
396RA8_INTERNAL static void internal_opf_first_event(const uint8_t* source,
397 size_t length,
398 const xml_event_t* event,
399 xml_reader_t* reader,
400 epub_book_t* book,
401 uint16_t* metadata_depth,
402 uint16_t* manifest_depth,
403 uint16_t* spine_depth,
404 xml_span_t* unique_id,
405 xml_span_t* out_spine_toc)
406{
407 if (event->depth == 0U) {
408 const priv_attr_t uid = priv_epub_xml_attr(source, length, event, "unique-identifier");
409 *unique_id = uid.present ? uid.span : (xml_span_t){};
410 } else if ((event->depth == 1U) &&
411 xml_span_local_equal(source, length, event->name, "metadata")) {
412 *metadata_depth = event->depth;
413 } else if ((event->depth == 1U) &&
414 xml_span_local_equal(source, length, event->name, "manifest")) {
415 *manifest_depth = event->depth;
416 } else if ((event->depth == 1U) && xml_span_local_equal(source, length, event->name, "spine")) {
417 *spine_depth = event->depth;
418 const priv_attr_t toc = priv_epub_xml_attr(source, length, event, "toc");
419 *out_spine_toc = toc.present ? toc.span : (xml_span_t){};
420 } else if (priv_epub_xml_direct_child(event->depth, *metadata_depth) &&
422 length,
423 priv_epub_xml_frame_name(reader, *metadata_depth),
424 "metadata")) {
425 internal_opf_metadata_child(source, length, event, reader, *unique_id, book);
426 } else if (priv_epub_xml_direct_child(event->depth, *manifest_depth) &&
428 length,
429 priv_epub_xml_frame_name(reader, *manifest_depth),
430 "manifest") &&
431 xml_span_local_equal(source, length, event->name, "item")) {
432 internal_manifest_item(source, length, event, book);
433 } else {
434 /* Elements outside the OPF sections consumed here are ignored. */
435 }
436}
437
438RA8_INTERNAL static ra8_err_t internal_opf_first(const uint8_t* source,
439 size_t length,
440 epub_book_t* book,
441 xml_span_t* out_spine_toc)
442{
443 xml_reader_t reader = {};
444 ra8_err_t err = priv_epub_xml_reader(&reader, source, length, &book->xml_workspace);
445 uint16_t metadata_depth = UINT16_MAX;
446 uint16_t manifest_depth = UINT16_MAX;
447 uint16_t spine_depth = UINT16_MAX;
448 xml_span_t unique_id = {};
449 while (err == k_ra8_ok) {
450 xml_event_t event = {};
451 err = xml_reader_next(&reader, &event);
452 // mcdc-deactivated: internal_opf_first pull-loop status gate; the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here and only the end-of-document condition varies.
453 if ((err != k_ra8_ok) || (event.kind == (uint8_t)k_xml_event_none)) {
454 break;
455 }
456 if (event.kind == (uint8_t)k_xml_event_text) {
457 internal_metadata_text(source, length, &event, &reader, book);
458 continue;
459 }
460 if (event.kind != (uint8_t)k_xml_event_start) {
461 continue;
462 }
464 length,
465 &event,
466 &reader,
467 book,
468 &metadata_depth,
469 &manifest_depth,
470 &spine_depth,
471 &unique_id,
472 out_spine_toc);
473 (void)spine_depth;
474 }
475 return internal_opf_status(err, manifest_depth, spine_depth);
476}
477
479internal_collect_spine(const uint8_t* source, size_t length, epub_book_t* book)
480{
481 xml_reader_t reader = {};
482 ra8_err_t err = priv_epub_xml_reader(&reader, source, length, &book->xml_workspace);
483 uint16_t spine_depth = UINT16_MAX;
485 while (err == k_ra8_ok) {
486 xml_event_t event = {};
487 err = xml_reader_next(&reader, &event);
488 // mcdc-deactivated: internal_collect_spine pull-loop status gate; the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here and only the end-of-document condition varies.
489 if ((err != k_ra8_ok) || (event.kind == (uint8_t)k_xml_event_none)) {
490 break;
491 }
492 if (event.kind != (uint8_t)k_xml_event_start) {
493 continue;
494 }
495 if ((event.depth == 1U) && xml_span_local_equal(source, length, event.name, "spine")) {
496 spine_depth = event.depth;
497 } else if (priv_epub_xml_direct_child(event.depth, spine_depth) &&
499 length,
500 priv_epub_xml_frame_name(&reader, spine_depth),
501 "spine") &&
502 xml_span_equal(source, length, event.name, "itemref")) {
503 const priv_attr_t idref = priv_epub_xml_attr(source, length, &event, "idref");
504 if (!idref.present) {
505 continue;
506 }
507 if (book->xml_workspace.reference_count >= (uint16_t)k_epub_max_chapters) {
508 return k_ra8_err_no_mem;
509 }
512 } else {
513 /* Other start elements do not contribute spine references. */
514 }
515 }
516 return err;
517}
518
520 size_t length,
521 epub_xml_workspace_t* workspace,
522 xml_span_t wanted,
523 xml_span_t* out_href)
524{
525 xml_reader_t reader = {};
526 ra8_err_t err = priv_epub_xml_reader(&reader, source, length, workspace);
527 uint16_t manifest_depth = UINT16_MAX;
528 while (err == k_ra8_ok) {
529 xml_event_t event = {};
530 err = xml_reader_next(&reader, &event);
531 // mcdc-deactivated: internal_manifest_lookup pull-loop status gate; the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here and only the end-of-document condition varies.
532 if ((err != k_ra8_ok) || (event.kind == (uint8_t)k_xml_event_none)) {
533 break;
534 }
535 if (event.kind != (uint8_t)k_xml_event_start) {
536 continue;
537 }
538 if ((event.depth == 1U) && xml_span_local_equal(source, length, event.name, "manifest")) {
539 manifest_depth = event.depth;
540 } else if (priv_epub_xml_direct_child(event.depth, manifest_depth) &&
542 length,
543 priv_epub_xml_frame_name(&reader, manifest_depth),
544 "manifest")) {
545 const priv_attr_t id = priv_epub_xml_attr(source, length, &event, "id");
546 const priv_attr_t href = priv_epub_xml_attr(source, length, &event, "href");
547 if (id.present && href.present && xml_decoded_equal(source, length, id.span, wanted)) {
548 *out_href = href.span;
549 return k_ra8_ok;
550 }
551 } else {
552 /* Other start elements cannot match this manifest lookup. */
553 }
554 }
555 return (err == k_ra8_ok) ? k_ra8_err_no_data : err;
556}
557
559internal_opf_shape(const uint8_t* source, size_t length, epub_xml_workspace_t* workspace)
560{
561 xml_reader_t reader = {};
562 ra8_err_t err = priv_epub_xml_reader(&reader, source, length, workspace);
563 bool saw_manifest = false;
564 bool saw_spine = false;
565 uint16_t spine_depth = UINT16_MAX;
566 uint16_t references = 0U;
567 while (err == k_ra8_ok) {
568 xml_event_t event = {};
569 err = xml_reader_next(&reader, &event);
570 // mcdc-deactivated: internal_opf_shape pull-loop status gate; the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here and only the end-of-document condition varies.
571 if ((err != k_ra8_ok) || (event.kind == (uint8_t)k_xml_event_none)) {
572 break;
573 }
574 if (event.kind != (uint8_t)k_xml_event_start) {
575 continue;
576 }
577 if ((event.depth == 1U) && xml_span_local_equal(source, length, event.name, "manifest")) {
578 saw_manifest = true;
579 } else if ((event.depth == 1U) && xml_span_local_equal(source, length, event.name, "spine")) {
580 saw_spine = true;
581 spine_depth = event.depth;
582 } else if (priv_epub_xml_direct_child(event.depth, spine_depth) &&
583 xml_span_equal(source, length, event.name, "itemref") &&
584 priv_epub_xml_attr(source, length, &event, "idref").present) {
585 if (references >= (uint16_t)k_epub_max_chapters) {
586 return k_ra8_err_no_mem;
587 }
588 ++references;
589 } else {
590 /* Other start elements do not affect the OPF shape. */
591 }
592 }
593 // mcdc-deactivated: internal_opf_shape completeness gate; the loop above exits only by end-of-document or by returning k_ra8_err_no_mem outright, and the public entry point validated this exact byte range with xml_validate() before this re-parse and the bounded pull reader is a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- so xml_reader_next() cannot fail here, so `err` is always k_ra8_ok here and only the two shape flags vary.
594 if ((err == k_ra8_ok) && (!saw_manifest || !saw_spine)) {
596 }
597 return err;
598}
599
624RA8_INTERNAL static ra8_err_t internal_opf_resolve_refs(const uint8_t* xml_bytes,
625 size_t xml_len,
626 xml_span_t spine_toc,
627 epub_book_t* book)
628{
629 ra8_err_t err = k_ra8_ok;
630 book->chapter_count = 0U;
631 // mcdc-deactivated: internal_opf_resolve_refs spine-reference loop status gate; `err` enters at k_ra8_ok -- priv_epub_xml_parse_opf calls this helper only from its `if (err == k_ra8_ok)` arm and the helper re-initialises it -- and the spine-reference loop's `err = found` arm is its only writer, which needs internal_manifest_lookup to return something other than k_ra8_ok or k_ra8_err_no_data. Its only such returns are propagated xml_reader_init()/xml_reader_next() failures over the exact byte range priv_epub_xml_parse_opf already accepted with xml_validate(), which runs that same bounded pull reader as a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- and xml_reader_init() rejects only a null pointer or a zero/oversized length, none of which can hold here because that same xml_validate() call accepted this (xml_bytes, xml_len) pair through that identical init. The status condition is therefore constant-true and only the reference-count bound varies.
632 for (uint16_t i = 0U; (err == k_ra8_ok) && (i < book->xml_workspace.reference_count); ++i) {
633 xml_span_t href = {};
634 const ra8_err_t found = internal_manifest_lookup(xml_bytes,
635 xml_len,
636 &book->xml_workspace,
637 book->xml_workspace.references[i],
638 &href);
639 if (found == k_ra8_ok) {
640 const uint16_t chapter = book->chapter_count;
641 priv_epub_xml_copy(xml_bytes,
642 xml_len,
643 href,
644 book->chapter_paths[chapter],
646 ++book->chapter_count;
647 } else if (found != k_ra8_err_no_data) {
648 err = found;
649 } else {
650 /* A missing optional manifest reference is skipped. */
651 }
652 }
653 // mcdc-deactivated: internal_opf_resolve_refs legacy-cover resolution gate; `err` enters at k_ra8_ok -- priv_epub_xml_parse_opf calls this helper only from its `if (err == k_ra8_ok)` arm and the helper re-initialises it -- and the spine-reference loop's `err = found` arm is its only writer, which needs internal_manifest_lookup to return something other than k_ra8_ok or k_ra8_err_no_data. Its only such returns are propagated xml_reader_init()/xml_reader_next() failures over the exact byte range priv_epub_xml_parse_opf already accepted with xml_validate(), which runs that same bounded pull reader as a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- and xml_reader_init() rejects only a null pointer or a zero/oversized length, none of which can hold here because that same xml_validate() call accepted this (xml_bytes, xml_len) pair through that identical init. The status condition is therefore constant-true and only the empty-cover-path and legacy-cover-id-present conditions vary.
654 if ((err == k_ra8_ok) && (book->cover_path[0] == '\0') &&
655 (book->xml_workspace.legacy_cover_id.length > 0U)) {
656 xml_span_t href = {};
657 if (internal_manifest_lookup(xml_bytes,
658 xml_len,
659 &book->xml_workspace,
661 &href) == k_ra8_ok) {
662 priv_epub_xml_copy(xml_bytes, xml_len, href, book->cover_path, sizeof(book->cover_path));
663 }
664 }
665 // mcdc-deactivated: internal_opf_resolve_refs spine `toc` fallback gate; `err` enters at k_ra8_ok -- priv_epub_xml_parse_opf calls this helper only from its `if (err == k_ra8_ok)` arm and the helper re-initialises it -- and the spine-reference loop's `err = found` arm is its only writer, which needs internal_manifest_lookup to return something other than k_ra8_ok or k_ra8_err_no_data. Its only such returns are propagated xml_reader_init()/xml_reader_next() failures over the exact byte range priv_epub_xml_parse_opf already accepted with xml_validate(), which runs that same bounded pull reader as a pure function of (source, source_len, workspace) -- the shim only ever writes the frame `consumer` field, which xml never reads back -- and xml_reader_init() rejects only a null pointer or a zero/oversized length, none of which can hold here because that same xml_validate() call accepted this (xml_bytes, xml_len) pair through that identical init. The status condition is therefore constant-true and only the non-nav TOC kind and the present spine `toc` attribute vary.
666 if ((err == k_ra8_ok) && (book->toc_kind != (uint8_t)k_epub_toc_nav) && (spine_toc.length > 0U)) {
667 xml_span_t href = {};
668 if (internal_manifest_lookup(xml_bytes, xml_len, &book->xml_workspace, spine_toc, &href) ==
669 k_ra8_ok) {
670 priv_epub_xml_copy(xml_bytes, xml_len, href, book->toc_path, sizeof(book->toc_path));
671 book->toc_kind = (uint8_t)k_epub_toc_ncx;
672 }
673 }
674 return err;
675}
676
677ra8_err_t priv_epub_xml_parse_opf(const uint8_t* xml_bytes, size_t xml_len, epub_book_t* book)
678{
679 if ((xml_bytes == nullptr) || (book == nullptr)) {
680 return k_ra8_err_null_ptr;
681 }
682 if (xml_len == 0U) {
684 }
685 ra8_err_t err = xml_validate(xml_bytes, xml_len, &book->xml_workspace.reader);
686 xml_span_t spine_toc = {};
687 if (err == k_ra8_ok) {
688 err = internal_opf_shape(xml_bytes, xml_len, &book->xml_workspace);
689 }
690 if (err == k_ra8_ok) {
691 err = internal_opf_first(xml_bytes, xml_len, book, &spine_toc);
692 }
693 if (err == k_ra8_ok) {
694 err = internal_collect_spine(xml_bytes, xml_len, book);
695 }
696 if (err == k_ra8_ok) {
697 err = internal_opf_resolve_refs(xml_bytes, xml_len, spine_toc, book);
698 }
699 return err;
700}
@ k_epub_max_manifest
Max <manifest> <item> entries kept (OPF order).
Definition epub.h:92
@ k_epub_max_fonts
Max embedded font manifest items kept.
Definition epub.h:91
@ k_epub_max_chapters
Max spine length we accept.
Definition epub.h:87
@ k_epub_max_path_len
Max href length (incl.
Definition epub.h:89
@ k_epub_toc_ncx
Parsed from an EPUB 2 NCX document.
Definition epub.h:126
@ k_epub_toc_nav
Parsed from an EPUB 3 nav.xhtml.
Definition epub.h:127
Private contracts for streamed EPUB XML consumers.
bool priv_epub_xml_attr_contains(const uint8_t *source, size_t source_len, priv_attr_t attribute, const char *needle)
Test whether a decoded attribute contains an ASCII literal.
priv_attr_t priv_epub_xml_attr(const uint8_t *source, size_t source_len, const xml_event_t *event, const char *name)
Find an exact-name attribute on one start event.
Private contracts for the EPUB container and OPF consumers.
static bool internal_font_type(const uint8_t *source, size_t source_len, priv_attr_t media)
Recognise one supported embedded-font media type.
void priv_epub_xml_copy(const uint8_t *source, size_t source_len, xml_span_t span, char *destination, size_t capacity)
Copy decoded text into a bounded EPUB field.
static ra8_err_t internal_manifest_lookup(const uint8_t *source, size_t length, epub_xml_workspace_t *workspace, xml_span_t wanted, xml_span_t *out_href)
xml_span_t priv_epub_xml_frame_name(const xml_reader_t *reader, uint16_t frame)
Return one live reader-frame name span.
static void internal_mark_metadata(const uint8_t *source, size_t source_len, const xml_event_t *event, xml_reader_t *reader, xml_span_t unique_id)
bool priv_epub_xml_ancestor_frame(uint16_t depth, uint16_t levels, uint16_t *out_frame)
Resolve a bounded ancestor frame index.
static void internal_opf_first_event(const uint8_t *source, size_t length, const xml_event_t *event, xml_reader_t *reader, epub_book_t *book, uint16_t *metadata_depth, uint16_t *manifest_depth, uint16_t *spine_depth, xml_span_t *unique_id, xml_span_t *out_spine_toc)
Classify one OPF start-tag event during the first parse pass.
uint16_t priv_epub_xml_ancestor_marker(const xml_reader_t *reader, uint16_t depth, uint16_t levels)
Return a checked ancestor consumer marker.
ra8_err_t priv_epub_xml_parse_container(const uint8_t *xml_bytes, size_t xml_len, epub_container_result_t *out, epub_xml_workspace_t *workspace)
Parse META-INF/container.xml and copy the rootfile path out.
static ra8_err_t internal_opf_resolve_refs(const uint8_t *xml_bytes, size_t xml_len, xml_span_t spine_toc, epub_book_t *book)
Resolve chapter, cover, and TOC hrefs from cross-referenced ids.
static ra8_err_t internal_opf_status(ra8_err_t err, uint16_t manifest_depth, uint16_t spine_depth)
static ra8_err_t internal_opf_first(const uint8_t *source, size_t length, epub_book_t *book, xml_span_t *out_spine_toc)
static void internal_opf_metadata_child(const uint8_t *source, size_t length, const xml_event_t *event, xml_reader_t *reader, xml_span_t unique_id, epub_book_t *book)
Record one direct metadata child of the OPF metadata element.
static ra8_err_t internal_opf_shape(const uint8_t *source, size_t length, epub_xml_workspace_t *workspace)
static ra8_err_t internal_collect_spine(const uint8_t *source, size_t length, epub_book_t *book)
ra8_err_t priv_epub_xml_find(const uint8_t *source, size_t length, epub_xml_workspace_t *workspace, const char *local, bool use_parent, uint32_t parent_offset, uint16_t parent_depth, xml_event_t *out)
Find the first local-name element under an optional direct parent.
ra8_err_t priv_epub_xml_reader(xml_reader_t *reader, const uint8_t *source, size_t length, epub_xml_workspace_t *workspace)
Initialise another validated EPUB pull pass.
bool priv_epub_xml_attr_contains(const uint8_t *source, size_t source_len, priv_attr_t attribute, const char *needle)
Test whether a decoded attribute contains an ASCII literal.
static void internal_metadata_text(const uint8_t *source, size_t source_len, const xml_event_t *event, const xml_reader_t *reader, epub_book_t *book)
bool priv_epub_xml_direct_child(uint16_t child, uint16_t parent)
Test whether a depth is exactly one level below another.
static void internal_manifest_item(const uint8_t *source, size_t source_len, const xml_event_t *event, epub_book_t *book)
static bool internal_font_type(const uint8_t *source, size_t source_len, priv_attr_t media)
ra8_err_t priv_epub_xml_parse_opf(const uint8_t *xml_bytes, size_t xml_len, epub_book_t *book)
Parse the OPF document into metadata, cover and spine.
priv_attr_t priv_epub_xml_attr(const uint8_t *source, size_t source_len, const xml_event_t *event, const char *name)
Find an exact-name attribute on one start event.
priv_marker_t
Consumer markers stored in live reader frames.
@ k_priv_mark_identifier
Frame is a fallback identifier.
@ k_priv_mark_creator
Frame is a Dublin Core creator.
@ k_priv_mark_language
Frame is a Dublin Core language.
@ k_priv_mark_identifier_match
Frame matches the package identifier.
@ k_priv_mark_none
Frame carries no metadata role.
@ k_priv_mark_title
Frame is a Dublin Core title.
Library-private contract for the bounded EPUB XML parser.
Annotation-attribute framework macros for ra8-firmware.
#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_no_data
No application data available (e.g.
Definition ra8_err.h:202
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ 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
char * strstr(const char *haystack, const char *needle)
Locate substring in string.
Opened EPUB book.
Definition epub.h:286
char author[k_epub_meta_len]
Dublin Core <dc:creator>.
Definition epub.h:327
char toc_path[k_epub_max_path_len]
Nav/NCX href (rel.
Definition epub.h:348
char chapter_paths[k_epub_max_chapters][k_epub_max_path_len]
Manifest hrefs (relative to OPF dir).
Definition epub.h:323
char language[k_epub_meta_len]
Dublin Core <dc:language>.
Definition epub.h:328
char cover_path[k_epub_max_path_len]
Path to cover image (or empty).
Definition epub.h:332
uint16_t chapter_count
Spine length actually stored.
Definition epub.h:321
epub_xml_workspace_t xml_workspace
Per-book XML parser state; never shared with another book.
Definition epub.h:311
char title[k_epub_meta_len]
Dublin Core <dc:title>.
Definition epub.h:326
uint8_t toc_kind
epub_toc_kind_t: source of the TOC.
Definition epub.h:349
epub_manifest_item_t manifest[k_epub_max_manifest]
Items, OPF order.
Definition epub.h:342
uint16_t embedded_font_count
Manifest font items found (<= cap).
Definition epub.h:335
uint16_t manifest_count
<manifest> <item> entries stored (<= cap).
Definition epub.h:340
char identifier[k_epub_meta_len]
Dublin Core <dc:identifier> (unique book id).
Definition epub.h:329
char embedded_font_paths[k_epub_max_fonts][k_epub_max_path_len]
Font hrefs (rel.
Definition epub.h:337
Out-parameter struct returned by priv_epub_xml_parse_container().
char opf_path[k_epub_max_path_len]
Rootfile path, NUL-terminated.
One <manifest> <item> entry, retained in OPF document order.
Definition epub.h:260
char media_type[k_epub_media_len]
Item media-type (or "" if absent).
Definition epub.h:263
char id[k_epub_id_len]
Manifest item id (or "" if absent).
Definition epub.h:261
char href[k_epub_max_path_len]
Item href (relative to the OPF dir).
Definition epub.h:262
Per-book bounded storage for strict OPF/container/TOC parsing.
Definition epub.h:105
xml_workspace_t reader
4096-byte open-element stack.
Definition epub.h:106
xml_span_t references[k_epub_max_chapters]
Spine idref spans retained between OPF passes.
Definition epub.h:108
xml_span_t legacy_cover_id
EPUB 2 cover manifest id.
Definition epub.h:109
uint16_t reference_count
Valid entries in references.
Definition epub.h:110
Optional attribute span.
xml_span_t span
Value span.
bool present
Attribute existed.
Mutable cursor for one start event's attributes.
Definition xml.h:87
Source-order attribute view.
Definition xml.h:81
xml_span_t value
Quoted value excluding delimiters.
Definition xml.h:83
xml_span_t name
Attribute name.
Definition xml.h:82
One XML pull event.
Definition xml.h:71
xml_span_t markup
Complete markup span, or text payload.
Definition xml.h:72
xml_span_t name
Element name for start/end.
Definition xml.h:73
uint16_t depth
Root is depth zero.
Definition xml.h:74
One open-element identity retained for close-tag validation.
Definition xml.h:50
uint16_t consumer
Consumer-owned while this frame is live.
Definition xml.h:53
uint16_t name_length
Element-name byte count.
Definition xml.h:52
uint32_t name_offset
Element-name offset in the source.
Definition xml.h:51
Pull-reader state; initialise before each pass.
Definition xml.h:93
xml_workspace_t * workspace
Caller-owned stack.
Definition xml.h:97
Immutable byte span expressed relative to the source.
Definition xml.h:44
uint32_t length
Byte count.
Definition xml.h:46
uint32_t offset
First byte offset.
Definition xml.h:45
xml_frame_t frames[k_xml_workspace_frames]
Open-element stack.
Definition xml.h:58
Bounded, caller-owned, no-heap XML pull reader.
bool xml_decoded_equal(const uint8_t *source, size_t source_len, xml_span_t left, xml_span_t right)
Compare two entity-decoded spans from the same immutable source.
Definition xml_decode.c:584
void xml_attr_begin(const xml_event_t *event, xml_attr_cursor_t *cursor)
Initialise source-order attribute iteration for a start event.
Definition xml.c:70
bool xml_span_local_equal(const uint8_t *source, size_t source_len, xml_span_t span, const char *literal)
Compare the namespace-local tail of a bounded span with an ASCII literal.
Definition xml_decode.c:565
ra8_err_t xml_attr_next(const uint8_t *source, size_t source_len, const xml_event_t *event, xml_attr_cursor_t *cursor, xml_attribute_t *out_attribute, bool *out_has_value)
Return the next source-order attribute.
Definition xml.c:181
@ k_xml_event_start
Element start.
Definition xml.h:64
@ k_xml_event_none
No event / end of source.
Definition xml.h:63
@ k_xml_event_text
Character-data run.
Definition xml.h:66
@ k_xml_event_end
Element end.
Definition xml.h:65
ra8_err_t xml_validate(const uint8_t *source, size_t source_len, xml_workspace_t *workspace)
Validate a complete document before any consumer mutation.
Definition xml.c:631
bool xml_span_equal(const uint8_t *source, size_t source_len, xml_span_t span, const char *literal)
Compare a bounded span with an exact ASCII literal.
Definition xml_decode.c:555
ra8_err_t xml_decode(const uint8_t *source, size_t source_len, xml_span_t span, char *destination, size_t capacity, size_t *out_length)
Entity-decode a source span into a bounded NUL-terminated buffer.
Definition xml_decode.c:511
ra8_err_t xml_reader_init(xml_reader_t *reader, const uint8_t *source, size_t source_len, xml_workspace_t *workspace)
Initialise a pull pass over immutable bytes.
Definition xml.c:571
ra8_err_t xml_reader_next(xml_reader_t *reader, xml_event_t *out_event)
Return the next semantic event and validate syntax incrementally.
Definition xml.c:590
ra8_err_t xml_decode_prefix(const uint8_t *source, size_t source_len, xml_span_t span, char *destination, size_t capacity, size_t *out_length)
Decode the longest complete prefix that fits the destination.
Definition xml_decode.c:527