code wiki / _hdl_build / nx_epub_nav.nx

nx_epub_nav.nx source

↩ module page · 201 lines · 9047 B

1// nx_epub_nav.nx -- Parses EPUB navigation documents into a flat, depth-tagged list of chapters for accurate table of contents rendering. 2const K_MAGIC_2048: i64 = 2048 3// nx_epub_nav.nx -- SOVEREIGN EPUB navigation-document parser (Reader-arc R-TOC). 4// 5// The "table of contents chapters wrong" root: nx_epub_book builds chapters[] from the OPF <spine> (reading 6// order), which is NOT the table of contents. The real TOC lives in a separate, usually HIERARCHICAL navigation 7// document: EPUB3 nav.xhtml (<nav epub:type="toc"> ... nested <ol>/<li>/<a>) or EPUB2 toc.ncx (nested <navPoint>). 8// This library parses EITHER into a flat, DEPTH-TAGGED entry list {depth, label, href} preserving nesting, so a 9// reader can render the true nested TOC and jump to the right chapter. Spine stays the reading order; this is the 10// navigation tree on top of it. 11// 12// Helpers are nav_-prefixed so this composes into nx_epub_book.nx (which has its own find_sub/attr_val) with NO 13// symbol clash. Pure Nishi + the caller's arenas; no syscalls of its own. license_tier: ORIGINAL 14 15func nav_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 16 17// does buf[i..] (within [0,n)) start with literal s? 18func nav_at(buf: *u8, i: i64, n: i64, s: *u8) -> i64 { 19 var k: i64 = 0 20 while s[k] != (0 as u8) { 21 if i + k >= n { return 0 } 22 if buf[i+k] != s[k] { return 0 } 23 k = k + 1 24 } 25 return 1 26} 27 28// first index >= start in buf[0,n) where needle matches; -1 if none. 29func nav_find_from(buf: *u8, n: i64, start: i64, needle: *u8) -> i64 { 30 let nl: i64 = nav_slen(needle) 31 if nl == 0 { return 0-1 } 32 var i: i64 = start 33 if i < 0 { i = 0 } 34 while i + nl <= n { 35 if nav_at(buf, i, n, needle) == 1 { return i } 36 i = i + 1 37 } 38 return 0-1 39} 40 41// '<a' followed by whitespace (a real anchor open tag), not '<abbr'/'<article'. 42func nav_is_anchor(buf: *u8, i: i64, n: i64) -> i64 { 43 if i + 2 >= n { return 0 } 44 if buf[i] != (0x3c as u8) { return 0 } // '<' 45 if buf[i+1] != (0x61 as u8) { return 0 } // 'a' 46 let c: i64 = buf[i+2] as i64 47 if c == 0x20 { return 1 } // space 48 if c == 0x09 { return 1 } // tab 49 if c == 0x0a { return 1 } // newline 50 if c == 0x0d { return 1 } // cr 51 return 0 52} 53 54// find attr (e.g. `href="`) within buf[start,limit), copy value until the next '"' into out; ret length or -1. 55func nav_attr_in(buf: *u8, start: i64, limit: i64, attr: *u8, out: *u8, outcap: i64) -> i64 { 56 let al: i64 = nav_slen(attr) 57 var i: i64 = start 58 var at: i64 = 0-1 59 while i + al <= limit { 60 var k: i64 = 0 61 var hit: i64 = 1 62 while k < al { if buf[i+k] != attr[k] { hit = 0; k = al } else { k = k + 1 } } 63 if hit == 1 { at = i; i = limit } else { i = i + 1 } 64 } 65 if at < 0 { return 0-1 } 66 var s: i64 = at + al 67 var o: i64 = 0 68 while s < limit { if buf[s] == (0x22 as u8) { break } if o < outcap-1 { out[o] = buf[s]; o = o + 1 } s = s + 1 } 69 out[o] = 0 as u8 70 return o 71} 72 73// copy text buf[start,end) into out, trimming leading/trailing ASCII whitespace; NUL-term; ret length. 74func nav_copy_trim(buf: *u8, start: i64, end: i64, out: *u8, outcap: i64) -> i64 { 75 var s: i64 = start 76 while s < end { let c: i64 = buf[s] as i64; if c==0x20 { s=s+1 } else { if c==0x0a { s=s+1 } else { if c==0x09 { s=s+1 } else { if c==0x0d { s=s+1 } else { break } } } } } 77 var e: i64 = end 78 while e > s { let c: i64 = buf[e-1] as i64; if c==0x20 { e=e-1 } else { if c==0x0a { e=e-1 } else { if c==0x09 { e=e-1 } else { if c==0x0d { e=e-1 } else { break } } } } } 79 var o: i64 = 0 80 var i: i64 = s 81 while i < e { if o < outcap-1 { out[o] = buf[i]; o = o + 1 } i = i + 1 } 82 out[o] = 0 as u8 83 return o 84} 85 86// store one entry into the caller arenas at slot cnt. larena/harena are byte arenas; loffs/hoffs the per-entry 87// start offsets; aoff[0]=label arena cursor, aoff[1]=href arena cursor (mutated). returns 0. 88func nav_store(depths: *i64, larena: *u8, loffs: *i64, harena: *u8, hoffs: *i64, aoff: *i64, cnt: i64, d: i64, label: *u8, href: *u8) -> i64 { 89 depths[cnt] = d 90 var la: i64 = aoff[0] 91 loffs[cnt] = la 92 var i: i64 = 0 93 while label[i] != (0 as u8) { larena[la] = label[i]; la = la + 1; i = i + 1 } 94 larena[la] = 0 as u8; la = la + 1 95 aoff[0] = la 96 var ha: i64 = aoff[1] 97 hoffs[cnt] = ha 98 i = 0 99 while href[i] != (0 as u8) { harena[ha] = href[i]; ha = ha + 1; i = i + 1 } 100 harena[ha] = 0 as u8; ha = ha + 1 101 aoff[1] = ha 102 return 0 103} 104 105// ===== EPUB3 nav.xhtml: <nav epub:type="toc"> nested <ol>/<li>/<a href>label</a> ===== 106// Writes depth-tagged entries; depth 0 = top-level. Returns count (<= cap). 107func nav_parse_xhtml(buf: *u8, n: i64, depths: *i64, larena: *u8, loffs: *i64, harena: *u8, hoffs: *i64, cap: i64) -> i64 { 108 var tpos: i64 = nav_find_from(buf, n, 0, "epub:type=\"toc\"" as *u8) 109 if tpos < 0 { tpos = nav_find_from(buf, n, 0, "epub:type='toc'" as *u8) } 110 if tpos < 0 { return 0 } 111 var i: i64 = nav_find_from(buf, n, tpos, "<ol" as *u8) 112 if i < 0 { return 0 } 113 let label: *u8 = sys_mmap(K_MAGIC_2048) 114 let href: *u8 = sys_mmap(K_MAGIC_2048) 115 let aoff: *i64 = sys_mmap(16) as *i64 116 aoff[0] = 0; aoff[1] = 0 117 var oldepth: i64 = 0 118 var cnt: i64 = 0 119 while i < n { 120 var adv: i64 = 1 121 if nav_at(buf, i, n, "<ol" as *u8) == 1 { oldepth = oldepth + 1; adv = 3 } 122 else { if nav_at(buf, i, n, "</ol>" as *u8) == 1 { 123 oldepth = oldepth - 1; adv = 5 124 if oldepth <= 0 { i = n } 125 } 126 else { if nav_is_anchor(buf, i, n) == 1 { 127 // tag end '>' 128 var te: i64 = i 129 while te < n { if buf[te] == (0x3e as u8) { break } te = te + 1 } 130 let hn: i64 = nav_attr_in(buf, i, te, "href=\"" as *u8, href, K_MAGIC_2048) 131 // label text between te+1 and next "</a>" 132 let ae: i64 = nav_find_from(buf, n, te, "</a>" as *u8) 133 var lend: i64 = ae 134 if lend < 0 { lend = te + 1 } 135 nav_copy_trim(buf, te + 1, lend, label, K_MAGIC_2048) 136 if hn > 0 { if cnt < cap { 137 var d: i64 = oldepth - 1 138 if d < 0 { d = 0 } 139 nav_store(depths, larena, loffs, harena, hoffs, aoff, cnt, d, label, href) 140 cnt = cnt + 1 141 } } 142 if ae >= 0 { adv = (ae + 4) - i } else { adv = 2 } 143 } } } 144 i = i + adv 145 } 146 return cnt 147} 148 149// ===== EPUB2 toc.ncx: <navMap> nested <navPoint><navLabel><text>L</text></navLabel><content src="H"/> ===== 150// Writes depth-tagged entries; depth 0 = top-level. Returns count (<= cap). 151func ncx_parse(buf: *u8, n: i64, depths: *i64, larena: *u8, loffs: *i64, harena: *u8, hoffs: *i64, cap: i64) -> i64 { 152 var s: i64 = nav_find_from(buf, n, 0, "<navMap" as *u8) 153 if s < 0 { return 0 } 154 let label: *u8 = sys_mmap(K_MAGIC_2048) 155 let href: *u8 = sys_mmap(K_MAGIC_2048) 156 let aoff: *i64 = sys_mmap(16) as *i64 157 aoff[0] = 0; aoff[1] = 0 158 var i: i64 = s 159 var npdepth: i64 = 0 160 var cnt: i64 = 0 161 var pending: i64 = 0 162 var have_l: i64 = 0 163 var have_h: i64 = 0 164 var pend_depth: i64 = 0 165 while i < n { 166 var adv: i64 = 1 167 if nav_at(buf, i, n, "<navPoint" as *u8) == 1 { 168 pend_depth = npdepth 169 npdepth = npdepth + 1 170 pending = 1; have_l = 0; have_h = 0 171 adv = 9 172 } 173 else { if nav_at(buf, i, n, "</navPoint>" as *u8) == 1 { npdepth = npdepth - 1; adv = 11; if npdepth < 0 { i = n } } 174 else { if nav_at(buf, i, n, "</navMap>" as *u8) == 1 { i = n } 175 else { if pending == 1 { if have_l == 0 { if nav_at(buf, i, n, "<text>" as *u8) == 1 { 176 let ls: i64 = i + 6 177 let le: i64 = nav_find_from(buf, n, ls, "</text>" as *u8) 178 var lend: i64 = le 179 if lend < 0 { lend = ls } 180 nav_copy_trim(buf, ls, lend, label, K_MAGIC_2048) 181 have_l = 1 182 if le >= 0 { adv = (le + 7) - i } else { adv = 6 } 183 } } } 184 if adv == 1 { if pending == 1 { if have_h == 0 { if nav_at(buf, i, n, "<content" as *u8) == 1 { 185 var te: i64 = i 186 while te < n { if buf[te] == (0x3e as u8) { break } te = te + 1 } 187 nav_attr_in(buf, i, te, "src=\"" as *u8, href, K_MAGIC_2048) 188 have_h = 1 189 adv = (te + 1) - i 190 } } } } 191 } } } 192 // emit when this navPoint has both label + href 193 if pending == 1 { if have_l == 1 { if have_h == 1 { if cnt < cap { 194 nav_store(depths, larena, loffs, harena, hoffs, aoff, cnt, pend_depth, label, href) 195 cnt = cnt + 1 196 pending = 0 197 } } } } 198 i = i + adv 199 } 200 return cnt 201}