code wiki / _hdl_build / nx_book_titles_gate.nx
nx_book_titles_gate.nx source
↩ module page · 107 lines · 10464 B
1// nx_book_titles_gate.nx -- liar-kill gate for the "TOC just shows Chapter 1" fix. Two layers:
2// PART 1 (unit): cs_extract_heading (shared by MOBI+KF8 via nx_chapter_split) must read <h1> OR <h2> OR <h3>,
3// stripping nested inline tags -- the reason ~390 Kindle TOCs degraded to "Chapter N" was reading
4// ONLY <h1>.
5// PART 2 (e2e): build a nav-LESS EPUB (so the reader falls back to chapters[].title = nx_epub_book's chap_title)
6// with a NESTED <h1><span>..</span></h1> chapter + an <h2>-only chapter, run the REAL nx_epub_book,
7// and assert book.json carries the real heading text -- NOT the "Chapter 0"/"Chapter 1" fallback.
8// Sovereign: opc_write + fork/exec, no shell. expect_exit: 0 license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_opc.nx"
11import "nx_chapter_split.nx" // cs_extract_heading (the shared Kindle title extractor)
12
13func tg_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
14func tg_n(v0: i64) -> i64 { var v: i64=v0; if v<0 { sys_write(1,"-" as *u8,1); v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0 {b[0]=48 as u8;k=1} while v>0 {b[k]=(48+(v%10)) as u8; v=v/10; k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k {o[j]=b[k-1-j];j=j+1} sys_write(1,o,k); return 0 }
15func tg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
16func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 }
17
18// out (NUL-term) equals literal exp ?
19func streq(out: *u8, exp: *u8) -> i64 {
20 var i: i64 = 0
21 while exp[i] != (0 as u8) { if (out[i]&0xff) != (exp[i]&0xff) { return 0 } i = i + 1 }
22 if out[i] != (0 as u8) { return 0 }
23 return 1
24}
25// does buf[0..n) contain NUL-term needle ?
26func hassub(buf: *u8, n: i64, needle: *u8) -> i64 {
27 let nl: i64 = tg_slen(needle); if nl == 0 { return 0 }
28 var i: i64 = 0
29 while i + nl <= n { var k: i64=0; var hit: i64=1; while k<nl { if (buf[i+k]&0xff)!=(needle[k]&0xff){hit=0;k=nl}else{k=k+1} } if hit==1 {return 1} i=i+1 }
30 return 0
31}
32func run2(path: *u8, a0: *u8, a1: *u8, redir: *u8) -> i64 {
33 let pid: i64 = sys_fork()
34 if pid == 0 {
35 let fd: i64 = sys_openat_wr(redir, 420)
36 if fd >= 0 { sys_dup3(fd, 1, 0); sys_dup3(fd, 2, 0) }
37 let argv: *i64 = sys_mmap(64) as *i64
38 argv[0] = path as i64; argv[1] = a0 as i64; argv[2] = a1 as i64; argv[3] = 0
39 sys_execve(path, argv, 0 as *i64)
40 sys_exit(127)
41 }
42 let st: *i64 = sys_mmap(16) as *i64
43 sys_wait4(pid, st, 0)
44 return (st[0] >> 8) & 0xff
45}
46
47func uchk(label: *u8, html: *u8, exp: *u8, pf: *i64) -> i64 {
48 let out: *u8 = sys_mmap(256)
49 cs_extract_heading(html, tg_slen(html), out, 256)
50 tg_p(" "); tg_p(label)
51 if streq(out, exp) == 1 { tg_p(" PASS\n"); pf[0]=pf[0]+1 } else { tg_p(" FAIL got='"); tg_p(out); tg_p("'\n"); pf[1]=pf[1]+1 }
52 return 0
53}
54
55func main() -> i64 {
56 tg_p("=== nx_book_titles_gate: real chapter titles from h1/h2/h3 (not 'Chapter N') ===\n" as *u8)
57 let pf: *i64 = sys_mmap(16) as *i64; pf[0]=0; pf[1]=0
58
59 // ---- PART 1: cs_extract_heading unit (MOBI/KF8 shared path) ----
60 uchk("h1 plain" as *u8, "<h1>Alpha</h1>" as *u8, "Alpha" as *u8, pf)
61 uchk("h2 only (no h1)" as *u8, "<p>x</p><h2>Beta Chapter</h2><p>y</p>" as *u8, "Beta Chapter" as *u8, pf)
62 uchk("h3 only" as *u8, "<h3>Gamma</h3>" as *u8, "Gamma" as *u8, pf)
63 uchk("h1 nested span stripped" as *u8, "<h1><span class=\"c\">Nested Title</span></h1>" as *u8, "Nested Title" as *u8, pf)
64 let none_out: *u8 = sys_mmap(64); let none_n: i64 = cs_extract_heading("<p>no heading here</p>" as *u8, 22, none_out, 64)
65 if none_n == 0 { tg_p(" no-heading -> empty PASS\n"); pf[0]=pf[0]+1 } else { tg_p(" no-heading FAIL\n"); pf[1]=pf[1]+1 }
66
67 // ---- PART 2: end-to-end EPUB (nav-less) chapter titles via nx_epub_book ----
68 ensure_dir("knowledge/fixtures" as *u8)
69 let names: *i64 = sys_mmap(8*8) as *i64
70 let datap: *i64 = sys_mmap(8*8) as *i64
71 let lens: *i64 = sys_mmap(8*8) as *i64
72 let mimetype: *u8 = "application/epub+zip" as *u8
73 let container: *u8 = "<?xml version=\"1.0\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n<rootfiles><rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/></rootfiles>\n</container>\n" as *u8
74 // NO nav item + NO ncx -> spine-fallback -> reader uses chapters[].title (chap_title). That is the path under test.
75 let opf: *u8 = "<?xml version=\"1.0\"?>\n<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"3.0\" unique-identifier=\"bookid\">\n<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n<dc:title>Titles Fixture</dc:title>\n<dc:creator>Lewis Carroll</dc:creator>\n<dc:identifier id=\"bookid\">titles-1</dc:identifier>\n</metadata>\n<manifest>\n<item id=\"ch1\" href=\"ch1.xhtml\" media-type=\"application/xhtml+xml\"/>\n<item id=\"ch2\" href=\"ch2.xhtml\" media-type=\"application/xhtml+xml\"/>\n</manifest>\n<spine>\n<itemref idref=\"ch1\"/>\n<itemref idref=\"ch2\"/>\n</spine>\n</package>\n" as *u8
76 // ch1: NESTED <h1><span>..</span></h1> (the elem_text-empties-it bug). + enough prose to clear the 2000-char floor.
77 let ch1: *u8 = "<?xml version=\"1.0\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>x</title></head><body>\n<h1><span class=\"ct\">Chapter One: Down the Rabbit-Hole</span></h1>\n<p>Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, and what is the use of a book, thought Alice, without pictures or conversations? So she was considering in her own mind, as well as she could, for the hot day made her feel very sleepy and stupid, whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.</p>\n<p>There was nothing so very remarkable in that; nor did Alice think it so very much out of the way to hear the Rabbit say to itself oh dear oh dear I shall be late. But when the Rabbit actually took a watch out of its waistcoat-pocket and looked at it and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket or a watch to take out of it.</p>\n</body></html>\n" as *u8
78 // ch2: <h2>-ONLY heading (no h1) -- proves the h2 fallback.
79 let ch2: *u8 = "<?xml version=\"1.0\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>x</title></head><body>\n<h2>Part Two: The Pool of Tears</h2>\n<p>In another moment down went Alice after the White Rabbit, never once considering how in the world she was to get out again. The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down what seemed to be a very deep well.</p>\n<p>Either the well was very deep, or she fell very slowly, for she had plenty of time as she went down to look about her, and to wonder what was going to happen next. First she tried to look down and make out what she was coming to, but it was too dark to see anything; then she looked at the sides of the well and noticed that they were filled with cupboards and book-shelves; here and there she saw maps and pictures hung upon pegs. She took down a jar from one of the shelves as she passed; it was labelled Orange Marmalade, but to her great disappointment it was empty: she did not like to drop the jar for fear of killing somebody underneath, so managed to put it into one of the cupboards as she fell past it. Down, down, down. Would the fall never come to an end? I wonder how many miles I have fallen by this time, she said aloud, for she had learnt several things of this sort in her lessons in the schoolroom.</p>\n</body></html>\n" as *u8
80 names[0]=("mimetype" as *u8) as i64; datap[0]=mimetype as i64; lens[0]=tg_slen(mimetype)
81 names[1]=("META-INF/container.xml" as *u8) as i64; datap[1]=container as i64; lens[1]=tg_slen(container)
82 names[2]=("OEBPS/content.opf" as *u8) as i64; datap[2]=opf as i64; lens[2]=tg_slen(opf)
83 names[3]=("OEBPS/ch1.xhtml" as *u8) as i64; datap[3]=ch1 as i64; lens[3]=tg_slen(ch1)
84 names[4]=("OEBPS/ch2.xhtml" as *u8) as i64; datap[4]=ch2 as i64; lens[4]=tg_slen(ch2)
85 let w: i64 = opc_write("knowledge/fixtures/nishi_titles.epub" as *u8, names, datap, lens, 5)
86 if w <= 0 { tg_p(" e2e FAIL fixture-write\n"); pf[1]=pf[1]+1 }
87 else {
88 // rebuild nx_epub_book first so this gate ALWAYS tests the current source (not a stale elf).
89 run2("_offc/nx_sov_build_run.elf\x00" as *u8, "nx_epub_book\x00" as *u8, 0 as *u8, "knowledge/status/titles_e2e_scratch.log\x00" as *u8)
90 let rc: i64 = run2("_offc/nx_epub_book.elf\x00" as *u8, "knowledge/fixtures/nishi_titles.epub\x00" as *u8, "titles_e2e\x00" as *u8, "knowledge/status/titles_e2e_scratch.log\x00" as *u8)
91 tg_p(" nx_epub_book exit="); tg_n(rc); tg_p("\n" as *u8)
92 let jp: *i64 = sys_mmap(8) as *i64; jp[0]=0
93 let jb: *u8 = sys_read_file("knowledge/staging/media/reader/titles_e2e/book.json\x00" as *u8, jp)
94 if (jb as i64) == 0 { tg_p(" e2e FAIL book.json-missing\n"); pf[1]=pf[1]+1 }
95 else {
96 let jn: i64 = jp[0]
97 if hassub(jb, jn, "Chapter One: Down the Rabbit-Hole" as *u8) == 1 { tg_p(" e2e nested-h1 title PASS\n"); pf[0]=pf[0]+1 } else { tg_p(" e2e nested-h1 title FAIL\n"); pf[1]=pf[1]+1 }
98 if hassub(jb, jn, "Part Two: The Pool of Tears" as *u8) == 1 { tg_p(" e2e h2-only title PASS\n"); pf[0]=pf[0]+1 } else { tg_p(" e2e h2-only title FAIL\n"); pf[1]=pf[1]+1 }
99 // teeth: the generic server fallback must NOT have been used for these chapters
100 if hassub(jb, jn, "\"title\":\"Chapter 0\"" as *u8) == 0 { tg_p(" e2e no 'Chapter 0' fallback PASS\n"); pf[0]=pf[0]+1 } else { tg_p(" e2e 'Chapter 0' fallback used FAIL\n"); pf[1]=pf[1]+1 }
101 }
102 }
103
104 tg_p("TITLES-GATE pass="); tg_n(pf[0]); tg_p(" fail="); tg_n(pf[1])
105 if pf[1] == 0 { tg_p(" verdict=GREEN\n"); sys_exit(0); return 0 }
106 tg_p(" verdict=RED\n"); sys_exit(1); return 1
107}