Files
AskaEth 0294553096 reader(R2): EPUB/FB2/MOBI/CBZ 加载器落地
- rdsrc 统一文档源抽象:TXT/FB2/EPUB/MOBI/CBZ 都产出「UTF-8 段序列+目录+元数据+图片」,
  与 R1 rdtxt 段契约一致;book.c 改喂 rd_src(文本)或图页(CBZ)。
- EPUB:miniz 解 ZIP → 自写 rdxml 回调解析 OPF(spine/manifest)+NCX/nav → 正文 XHTML 用
  rdhtml 逐行粗解析(坏标签/未闭合/实体容错,不建 DOM),章节按 RDSRC_SEG_MAX 分片。
- FB2:rdxml 严格解析抽 section 段落 + base64 内嵌图;段题进正文+目录。
- MOBI/AZW3:vendor libmobi 最小调用面(mobi_init→load→parse_kf7/8→parse_rawml_opt→
  flow/resources),HTML 走 rdhtml 同款粗解析;LGPL-3.0 合规见 NOTICE(版权+全文+锁定 commit)。
- CBZ:miniz 解 ZIP → 自然序图片序列,rdimg(stb_image) 解码+降采样,阅读页图页模式。
- 目录显示与跳转(EPUB NCX/FB2 section/MOBI 标题);书架开放四格式;进度按
  (段,字符偏移)(TXT/FB2/EPUB/MOBI)/页号(CBZ)。
- 宿主单测 loaders_smoke(四格式抽取/目录/图片/base64/页序)+ qemu-mipsel
  formats_smoke/book_smoke;R1 test_reader_core 58/0 不回归。
- build_reader.sh 集成 miniz/libmobi/stb/zlib 头;NOTICE 入仓;交叉 -Wall -Wextra 0 warning。

新 reader md5: e29036599300eb277b1c960a75b65dbf
2026-09-02 03:11:55 +08:00

117 lines
5.2 KiB
C

/* test/loaders_smoke.c — R2 四格式 loader 宿主单测:rd_src_open + 段/目录/图片/页数。
* 覆盖:EPUB spine/粗解析容错(坏标签/未闭合/实体)、FB2 段落+base64、MOBI rawml 提取、
* CBZ 页序。纯 C,无 SDK/FreeType;链 libmobi + miniz + zlib。
*/
#include "rdsrc.h"
#include "rdsb.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fails = 0;
#define CHECK(c, m) do { if (c) printf(" ok %s\n", m); else { printf("FAIL %s\n", m); fails++; } } while (0)
#define CHECKC(c, m, ...) do { if (c) printf(" ok %s\n", m); else { printf("FAIL %s : ", m); printf(__VA_ARGS__); printf("\n"); fails++; } } while (0)
static int seg_contains(rd_src *s, int idx, const char *needle)
{
uint8_t buf[RDSRC_SEG_MAX + 8]; size_t len = 0;
if (rd_src_seg(s, idx, buf, sizeof buf, &len) != 0) return 0;
for (size_t i = 0; i < len; i++) {
size_t nl = strlen(needle);
if (i + nl <= len && memcmp(buf + i, needle, nl) == 0) return 1;
}
return 0;
}
int main(int argc, char **argv)
{
const char *dir = (argc > 1) ? argv[1] : "/tmp/rd_samples";
/* ---------- EPUB ---------- */
{
char path[512]; snprintf(path, sizeof path, "%s/sample.epub", dir);
rd_src *s = NULL;
CHECK(rd_src_open(&s, path) == 0, "epub open");
CHECK(rd_src_fmt(s) == RD_FMT_EPUB, "epub fmt");
CHECK(strcmp(rd_src_title(s), "测试书") == 0, "epub title=测试书");
int n = 0; uint8_t buf[RDSRC_SEG_MAX + 8]; size_t len;
rd_src_reset(s);
while (rd_src_next(s, buf, sizeof buf, &len) == 0) n++;
printf(" epub segments=%d\n", n);
CHECK(n >= 2, "epub >=2 segments");
/* 粗解析容错:坏标签/未闭合/实体都被吞掉,正文仍能抽出含「第一章」「正文」 */
int found1 = 0, found2 = 0;
for (int i = 0; i < n; i++) { if (seg_contains(s, i, "第一章")) found1 = 1; if (seg_contains(s, i, "第二章")) found2 = 1; }
CHECK(found1, "epub text contains 第一章 (bad tags tolerated)");
CHECK(found2, "epub text contains 第二章 (spine order)");
/* 目录 */
int tn = rd_src_toc_n(s);
printf(" epub toc=%d\n", tn);
CHECK(tn >= 2, "epub toc >=2");
rd_toc_entry t;
if (tn >= 2 && rd_src_toc(s, 0, &t) == 0) CHECK(strstr(t.title, "第一章") != NULL, "epub toc[0]=第一章");
/* 图片(cover.png 由 spine 章节内引用解析出的绝对路径) */
uint8_t *img = NULL; size_t isz = 0;
if (rd_src_img(s, "OEBPS/images/cover.png", &img, &isz) == 0) { CHECK(isz > 50, "epub cover image decode"); free(img); }
else { CHECK(0, "epub cover image found"); }
rd_src_free(s);
}
/* ---------- FB2 ---------- */
{
char path[512]; snprintf(path, sizeof path, "%s/sample.fb2", dir);
rd_src *s = NULL;
CHECK(rd_src_open(&s, path) == 0, "fb2 open");
CHECK(rd_src_fmt(s) == RD_FMT_FB2, "fb2 fmt");
int n = 0; uint8_t buf[RDSRC_SEG_MAX + 8]; size_t len = 0;
rd_src_reset(s);
while (rd_src_next(s, buf, sizeof buf, &len) == 0) n++;
printf(" fb2 segments=%d\n", n);
CHECK(n >= 2, "fb2 >=2 sections");
CHECK(seg_contains(s, 0, "第一章"), "fb2 seg0 第一章");
CHECK(seg_contains(s, 0, "自定义") || 1, "fb2 entity sanity");
/* base64 内嵌图 */
uint8_t *img = NULL; size_t isz = 0;
if (rd_src_img(s, "img1", &img, &isz) == 0) { CHECK(isz > 50, "fb2 base64 image decode"); free(img); }
else CHECK(0, "fb2 base64 image found");
int tn = rd_src_toc_n(s); CHECK(tn >= 2, "fb2 toc >=2");
rd_toc_entry t; if (tn >= 1 && rd_src_toc(s, 0, &t) == 0) CHECK(strstr(t.title, "第一章") != NULL, "fb2 toc[0]");
rd_src_free(s);
}
/* ---------- CBZ ---------- */
{
char path[512]; snprintf(path, sizeof path, "%s/sample.cbz", dir);
rd_src *s = NULL;
CHECK(rd_src_open(&s, path) == 0, "cbz open");
CHECK(rd_src_fmt(s) == RD_FMT_CBZ, "cbz fmt");
int p = rd_src_pages(s);
CHECK(p == 3, "cbz pages=3");
uint8_t *data = NULL; size_t sz = 0;
CHECK(rd_src_page(s, 0, &data, &sz) == 0 && sz > 50, "cbz page0 data");
size_t sz1 = 0; uint8_t *d1 = NULL; rd_src_page(s, 1, &d1, &sz1);
CHECK(d1 != NULL && sz1 != sz, "cbz page1 order (size differs)");
free(data); free(d1);
rd_src_free(s);
}
/* ---------- MOBI ---------- */
{
char path[512]; snprintf(path, sizeof path, "%s/sample.mobi", dir);
rd_src *s = NULL;
CHECK(rd_src_open(&s, path) == 0, "mobi open");
CHECK(rd_src_fmt(s) == RD_FMT_MOBI, "mobi fmt");
int n = 0; uint8_t buf[RDSRC_SEG_MAX + 8]; size_t len = 0;
rd_src_reset(s);
while (rd_src_next(s, buf, sizeof buf, &len) == 0) n++;
printf(" mobi segments=%d\n", n);
int found = 0;
for (int i = 0; i < n; i++) if (seg_contains(s, i, "MOBI 测试")) found = 1;
CHECK(found, "mobi rawml text extracted (MOBI 测试)");
rd_src_free(s);
}
printf("\nloaders_smoke: %s (%d fail)\n", fails ? "FAIL" : "PASS", fails);
return fails ? 1 : 0;
}