code wiki / _hdl_build / nx_xlsx.nx

nx_xlsx.nx source

↩ module page · 1283 lines · 60962 B

1// nx_xlsx.nx -- SOVEREIGN MS-Excel (.xlsx / SpreadsheetML) interop for the Nishi Office suite. A .xlsx is an OPC 2// package (framed by the shared nx_opc, same ZIP as .docx) with 5 parts: 3// [Content_Types].xml, _rels/.rels, xl/workbook.xml, xl/_rels/workbook.xml.rels, xl/worksheets/sheet1.xml 4// fromtsv : a tab-separated grid -> a valid Excel-openable .xlsx (numeric cells as <v>, text as inlineStr; 5// cell text XML-escaped). Use case: an estate asset SCHEDULE / valuation workbook. 6// read : a .xlsx -> the sheet1 grid back as TSV (handles our inlineStr + numeric cells). 7// Sovereignty = our own SpreadsheetML; rigor = the harness proves a 3rd-party (python zipfile) opens our .xlsx 8// with valid CRC-32s and the expected cell values. 9// HONEST LIMITATION: read supports inlineStr + numeric <v>; a real Excel file that uses a sharedStrings table 10// (t="s") would yield string INDICES not text -- sharedStrings read is a follow-on. Sparse columns are emitted 11// in document order (dense round-trip is exact; gaps are not repositioned). 12// nx_xlsx fromtsv <in.tsv> <out.xlsx> | nx_xlsx read <in.xlsx> 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 16import "nx_opc.nx" 17const XF_MAGIC_10000: i64 = 10000 18const XF_MAGIC_8388624: i64 = 8388624 19const XF_MAGIC_4194320: i64 = 4194320 20const XF_MAGIC_4194304: i64 = 4194304 21const XF_MAGIC_8388608: i64 = 8388608 22const XF_MAGIC_1048592: i64 = 1048592 23const XF_MAGIC_16384: i64 = 16384 24const XF_MAGIC_33554432: i64 = 33554432 25const XF_MAGIC_1048576: i64 = 1048576 26 27func xl_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 28// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 29// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 30// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 31// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 32func xl_num(v: i64) -> i64 { nxi_out(v); return 0 } 33func xl_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 } 34func xl_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 35func xl_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[off+i]=s[i]; i=i+1 } return off+i } 36func xl_uint(dst: *u8, off: i64, v: i64) -> i64 { var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var o: i64=off; var i: i64=0; while i<k{dst[o]=t[k-1-i];o=o+1;i=i+1} return o } 37func xl_match(buf: *u8, pos: i64, n: i64, pat: *u8) -> i64 { 38 var k: i64=0; while pat[k]!=(0 as u8) { if pos+k>=n { return 0 } if buf[pos+k]!=pat[k] { return 0 } k=k+1 } return 1 39} 40// bijective base-26 column label (0->A, 25->Z, 26->AA) appended to dst 41func xl_col(dst: *u8, off: i64, idx: i64) -> i64 { 42 var n: i64=idx+1; let t: *u8=sys_mmap(8); var k: i64=0 43 while n>0 { let rem: i64=(n-1)%26; t[k]=(65+rem) as u8; k=k+1; n=(n-1)/26 } 44 var o: i64=off; var i: i64=k-1; while i>=0 { dst[o]=t[i]; o=o+1; i=i-1 } return o 45} 46// is src[soff..soff+slen) a plain number? (optional leading '-', digits, at most one '.') 47func xl_is_numeric(src: *u8, soff: i64, slen: i64) -> i64 { 48 if slen<=0 { return 0 } 49 var dots: i64=0; var digits: i64=0; var i: i64=0 50 while i<slen { 51 let c: i64=src[soff+i] as i64 52 var ok: i64=0 53 if c>=48 { if c<=57 { ok=1; digits=digits+1 } } 54 if c==45 { if i==0 { ok=1 } } 55 if c==46 { dots=dots+1; if dots<=1 { ok=1 } } 56 if ok==0 { return 0 } 57 i=i+1 58 } 59 if digits==0 { return 0 } 60 return 1 61} 62// append src[soff..soff+slen) escaping XML & < > (and " for safety) 63func xl_escape_region(out: *u8, o: i64, src: *u8, soff: i64, slen: i64) -> i64 { 64 var oo: i64=o; var k: i64=0 65 while k<slen { 66 let c: i64=src[soff+k] as i64 67 if c==38 { oo=xl_cat(out,oo,"&amp;" as *u8) } else { if c==60 { oo=xl_cat(out,oo,"&lt;" as *u8) } else { if c==62 { oo=xl_cat(out,oo,"&gt;" as *u8) } else { out[oo]=src[soff+k]; oo=oo+1 } } } 68 k=k+1 69 } 70 return oo 71} 72// emit one cell <c r="<col><row>" ...> for src[soff..soff+slen) 73func xl_emit_cell(out: *u8, o: i64, col: i64, row: i64, src: *u8, soff: i64, slen: i64) -> i64 { 74 var oo: i64=o 75 oo=xl_cat(out,oo,"<c r=\"" as *u8); oo=xl_col(out,oo,col); oo=xl_uint(out,oo,row); oo=xl_cat(out,oo,"\"" as *u8) 76 if slen>0 { if xl_is_numeric(src,soff,slen)==1 { 77 oo=xl_cat(out,oo,"><v>" as *u8) 78 var k: i64=0; while k<slen { out[oo]=src[soff+k]; oo=oo+1; k=k+1 } 79 oo=xl_cat(out,oo,"</v></c>" as *u8) 80 return oo 81 } } 82 oo=xl_cat(out,oo," t=\"inlineStr\"><is><t xml:space=\"preserve\">" as *u8) 83 oo=xl_escape_region(out,oo,src,soff,slen) 84 oo=xl_cat(out,oo,"</t></is></c>" as *u8) 85 return oo 86} 87// build xl/worksheets/sheet1.xml from a tab-separated grid; return length 88func xl_build_sheet(tsv: *u8, tn: i64, out: *u8) -> i64 { 89 var o: i64=xl_cat(out,0,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><sheetData>" as *u8) 90 var rownum: i64=0; var pos: i64=0 91 while pos<tn { 92 var le: i64=tn; var i: i64=pos; while i<tn { if tsv[i]==(10 as u8) { le=i; break } i=i+1 } 93 if le>pos { 94 var lineend: i64=le; if tsv[lineend-1]==(13 as u8) { lineend=lineend-1 } 95 rownum=rownum+1 96 o=xl_cat(out,o,"<row r=\"" as *u8); o=xl_uint(out,o,rownum); o=xl_cat(out,o,"\">" as *u8) 97 var cs: i64=pos; var col: i64=0; var j: i64=pos 98 while j<=lineend { 99 var sep: i64=0 100 if j==lineend { sep=1 } else { if tsv[j]==(9 as u8) { sep=1 } } 101 if sep==1 { o=xl_emit_cell(out, o, col, rownum, tsv, cs, j-cs); col=col+1; cs=j+1 } 102 j=j+1 103 } 104 o=xl_cat(out,o,"</row>" as *u8) 105 } 106 pos=le+1 107 } 108 o=xl_cat(out,o,"</sheetData></worksheet>" as *u8) 109 return o 110} 111 112// ---- FORMULAS (OF-S3): '='-cells in the TSV become real SpreadsheetML <f> formula cells with a computed 113// cached <v> (exactly how Excel stores them). Values are Q4 FIXED-POINT (value x 10000, 4 decimal places; 114// integer arithmetic, division floors) -- honest scope: SUM/AVERAGE/MIN/MAX/COUNT + - * / ( ) unary-, 115// A1/AB12 refs and A1:B9 ranges; formula-referencing-formula chains memoized; cycles/div-0/bad-ref/syntax 116// LOUD-FAIL the write (never a silently wrong workbook). ---- 117const XF_MAXC: i64 = 64 118const XF_MAXR: i64 = 1024 119 120struct XfGrid { 121 tsv: *u8, 122 gv: *i64, // Q4 value per cell [r*XF_MAXC+c] 123 gk: *u8, // 0 empty | 1 number | 2 text | 3 formula 124 gs: *u8, // formula state: 0 todo | 1 in-progress | 2 done 125 gfo: *i64, // formula span offset (past '=') 126 gfl: *i64, // formula span length 127 nrows: i64, 128 ncols: i64, 129 err: i64, // 0 ok | 1 syntax | 2 bad-ref/value | 3 cycle | 4 div-0 130 errow: i64, 131 ercol: i64 132} 133 134func xf_grid_new(tsv: *u8) -> *XfGrid { 135 let g: *XfGrid = sys_mmap(96) as *XfGrid 136 g.tsv = tsv 137 g.gv = sys_mmap(8*XF_MAXR*XF_MAXC) as *i64 138 g.gk = sys_mmap(XF_MAXR*XF_MAXC) 139 g.gs = sys_mmap(XF_MAXR*XF_MAXC) 140 g.gfo = sys_mmap(8*XF_MAXR*XF_MAXC) as *i64 141 g.gfl = sys_mmap(8*XF_MAXR*XF_MAXC) as *i64 142 g.nrows = 0 143 g.ncols = 0 144 g.err = 0 145 g.errow = 0 146 g.ercol = 0 147 return g 148} 149 150func xf_ws(s: *u8, ip: *i64, e: i64) -> i64 { 151 while ip[0] < e { if s[ip[0]]==(32 as u8) { ip[0]=ip[0]+1 } else { return 0 } } 152 return 0 153} 154 155// verified-numeric text -> Q4 (sign, int, up to 4 frac digits; extras truncated) 156func xf_q4_of(s: *u8, off: i64, len: i64) -> i64 { 157 var i: i64=0 158 var neg: i64=0 159 if len>0 { if s[off]==(45 as u8) { neg=1; i=1 } } 160 var v: i64=0 161 while i<len { let c: i64=s[off+i] as i64; if c==46 { break } v=v*10+(c-48); i=i+1 } 162 v=v*XF_MAGIC_10000 163 if i<len { if s[off+i]==(46 as u8) { 164 i=i+1 165 var sc: i64=1000 166 while i<len { if sc>0 { let c2: i64=s[off+i] as i64; v=v+(c2-48)*sc; sc=sc/10 } i=i+1 } 167 } } 168 if neg==1 { return 0-v } 169 return v 170} 171 172// Q4 -> decimal text (frac trimmed) 173func xf_fmtq4(dst: *u8, off: i64, v: i64) -> i64 { 174 var o: i64=off 175 var m: i64=v 176 if m<0 { dst[o]=45 as u8; o=o+1; m=0-m } 177 let ipart: i64=m/XF_MAGIC_10000 178 var fpart: i64=m%XF_MAGIC_10000 179 o=xl_uint(dst,o,ipart) 180 if fpart>0 { 181 dst[o]=46 as u8; o=o+1 182 let t: *u8=sys_mmap(8) 183 t[0]=(48+fpart/1000) as u8 184 t[1]=(48+(fpart/100)%10) as u8 185 t[2]=(48+(fpart/10)%10) as u8 186 t[3]=(48+fpart%10) as u8 187 var last: i64=3 188 while last>0 { if t[last]==(48 as u8) { last=last-1 } else { break } } 189 var q: i64=0 190 while q<=last { dst[o]=t[q]; o=o+1; q=q+1 } 191 } 192 return o 193} 194 195// parse an A1/AB12 ref at *ip; rc[0]=col rc[1]=row (0-based); 1 ok, 0 not-a-ref (ip restored by caller) 196func xf_refparse(s: *u8, ip: *i64, e: i64, rc: *i64) -> i64 { 197 var i: i64=ip[0] 198 var col: i64=0 199 var nl: i64=0 200 while i<e { let c: i64=s[i] as i64; if c>=65 { if c<=90 { col=col*26+(c-64); nl=nl+1; i=i+1 } else { break } } else { break } } 201 if nl<1 { return 0 } 202 if nl>2 { return 0 } 203 var row: i64=0 204 var nd: i64=0 205 while i<e { let c2: i64=s[i] as i64; if c2>=48 { if c2<=57 { row=row*10+(c2-48); nd=nd+1; i=i+1 } else { break } } else { break } } 206 if nd<1 { return 0 } 207 if row<1 { return 0 } 208 rc[0]=col-1 209 rc[1]=row-1 210 ip[0]=i 211 return 1 212} 213 214// numeric value of cell (r,c): numbers directly; formulas evaluated on demand (memo + cycle detect); 215// empty/text/out-of-range -> err 2 (LOUD beats silently-wrong money). 216func xf_cellnum(g: *XfGrid, r: i64, c: i64) -> i64 { 217 if g.err != 0 { return 0 } 218 if r<0 { g.err=2; return 0 } 219 if c<0 { g.err=2; return 0 } 220 if r>=XF_MAXR { g.err=2; return 0 } 221 if c>=XF_MAXC { g.err=2; return 0 } 222 let idx: i64=r*XF_MAXC+c 223 let k: i64=g.gk[idx] as i64 224 if k==1 { return g.gv[idx] } 225 if k==3 { 226 let st: i64=g.gs[idx] as i64 227 if st==2 { return g.gv[idx] } 228 if st==1 { g.err=3; return 0 } 229 xf_evalcell(g, r, c) 230 if g.err != 0 { return 0 } 231 return g.gv[idx] 232 } 233 g.err=2 234 return 0 235} 236 237func xf_factor(g: *XfGrid, ip: *i64, e: i64) -> i64 { 238 if g.err != 0 { return 0 } 239 let s: *u8=g.tsv 240 xf_ws(s, ip, e) 241 if ip[0]>=e { g.err=1; return 0 } 242 let c0: i64=s[ip[0]] as i64 243 if c0==40 { 244 ip[0]=ip[0]+1 245 let v: i64=xf_expr(g, ip, e) 246 xf_ws(s, ip, e) 247 if ip[0]<e { if s[ip[0]]==(41 as u8) { ip[0]=ip[0]+1; return v } } 248 g.err=1 249 return 0 250 } 251 if c0==45 { ip[0]=ip[0]+1; let v2: i64=xf_factor(g, ip, e); return 0-v2 } 252 var isnum: i64=0 253 if c0>=48 { if c0<=57 { isnum=1 } } 254 if c0==46 { isnum=1 } 255 if isnum==1 { 256 let st: i64=ip[0] 257 var i: i64=ip[0] 258 var dots: i64=0 259 while i<e { let cc: i64=s[i] as i64; var ok: i64=0; if cc>=48 { if cc<=57 { ok=1 } } if cc==46 { dots=dots+1; if dots<=1 { ok=1 } } if ok==1 { i=i+1 } else { break } } 260 ip[0]=i 261 return xf_q4_of(s, st, i-st) 262 } 263 if c0>=65 { if c0<=90 { 264 // FUNC( ... ) or a cell ref 265 var la: i64=ip[0] 266 var nlet: i64=0 267 while la<e { let cl: i64=s[la] as i64; if cl>=65 { if cl<=90 { nlet=nlet+1; la=la+1 } else { break } } else { break } } 268 var isfunc: i64=0 269 if la<e { if s[la]==(40 as u8) { isfunc=1 } } 270 if isfunc==1 { 271 // identify: SUM AVERAGE MIN MAX COUNT 272 var fid: i64=0 273 if nlet==3 { if s[ip[0]]==(83 as u8) { if s[ip[0]+1]==(85 as u8) { if s[ip[0]+2]==(77 as u8) { fid=1 } } } } 274 if nlet==7 { if s[ip[0]]==(65 as u8) { fid=2 } } 275 if nlet==3 { if s[ip[0]]==(77 as u8) { if s[ip[0]+1]==(73 as u8) { fid=3 } } } 276 if nlet==3 { if s[ip[0]]==(77 as u8) { if s[ip[0]+1]==(65 as u8) { fid=4 } } } 277 if nlet==5 { if s[ip[0]]==(67 as u8) { fid=5 } } 278 if fid==0 { g.err=1; return 0 } 279 ip[0]=la+1 280 var sum: i64=0 281 var cnt: i64=0 282 var mn: i64=0 283 var mx: i64=0 284 var go: i64=1 285 while go==1 { 286 if g.err != 0 { return 0 } 287 xf_ws(s, ip, e) 288 // arg = RANGE (ref:ref) | expr 289 let save: i64=ip[0] 290 let rc1: *i64=sys_mmap(32) as *i64 291 var isrange: i64=0 292 if xf_refparse(s, ip, e, rc1)==1 { if ip[0]<e { if s[ip[0]]==(58 as u8) { 293 ip[0]=ip[0]+1 294 let rc2: *i64=sys_mmap(32) as *i64 295 if xf_refparse(s, ip, e, rc2)==0 { g.err=1; return 0 } 296 isrange=1 297 var r1: i64=rc1[1] 298 var r2: i64=rc2[1] 299 var c1: i64=rc1[0] 300 var c2: i64=rc2[0] 301 if r2<r1 { let tt: i64=r1; r1=r2; r2=tt } 302 if c2<c1 { let t2: i64=c1; c1=c2; c2=t2 } 303 var rr: i64=r1 304 while rr<=r2 { 305 var cc2: i64=c1 306 while cc2<=c2 { 307 let idx2: i64=rr*XF_MAXC+cc2 308 var kk: i64=0 309 if rr<XF_MAXR { if cc2<XF_MAXC { kk=g.gk[idx2] as i64 } } 310 var vv: i64=0 311 var incl: i64=0 312 if kk==1 { vv=g.gv[idx2]; incl=1 } 313 if kk==3 { vv=xf_cellnum(g, rr, cc2); incl=1; if g.err != 0 { return 0 } } 314 if incl==1 { 315 sum=sum+vv 316 if cnt==0 { mn=vv; mx=vv } else { if vv<mn { mn=vv } if vv>mx { mx=vv } } 317 cnt=cnt+1 318 } 319 cc2=cc2+1 320 } 321 rr=rr+1 322 } 323 } } } 324 if isrange==0 { 325 ip[0]=save 326 let v3: i64=xf_expr(g, ip, e) 327 if g.err != 0 { return 0 } 328 sum=sum+v3 329 if cnt==0 { mn=v3; mx=v3 } else { if v3<mn { mn=v3 } if v3>mx { mx=v3 } } 330 cnt=cnt+1 331 } 332 xf_ws(s, ip, e) 333 if ip[0]>=e { g.err=1; return 0 } 334 if s[ip[0]]==(44 as u8) { ip[0]=ip[0]+1 } else { 335 if s[ip[0]]==(41 as u8) { ip[0]=ip[0]+1; go=0 } else { g.err=1; return 0 } 336 } 337 } 338 if fid==1 { return sum } 339 if fid==2 { if cnt==0 { g.err=4; return 0 } return sum/cnt } 340 if fid==3 { if cnt==0 { g.err=2; return 0 } return mn } 341 if fid==4 { if cnt==0 { g.err=2; return 0 } return mx } 342 return cnt*XF_MAGIC_10000 343 } 344 // plain cell ref 345 let rc: *i64=sys_mmap(32) as *i64 346 if xf_refparse(s, ip, e, rc)==1 { return xf_cellnum(g, rc[1], rc[0]) } 347 g.err=1 348 return 0 349 } } 350 g.err=1 351 return 0 352} 353 354func xf_term(g: *XfGrid, ip: *i64, e: i64) -> i64 { 355 var v: i64=xf_factor(g, ip, e) 356 var go: i64=1 357 while go==1 { 358 if g.err != 0 { return 0 } 359 xf_ws(g.tsv, ip, e) 360 var op: i64=0 361 if ip[0]<e { op=g.tsv[ip[0]] as i64 } 362 if op==42 { 363 ip[0]=ip[0]+1 364 let r: i64=xf_factor(g, ip, e) 365 v=(v*r)/XF_MAGIC_10000 366 } else { if op==47 { 367 ip[0]=ip[0]+1 368 let r2: i64=xf_factor(g, ip, e) 369 if r2==0 { g.err=4; return 0 } 370 v=(v*XF_MAGIC_10000)/r2 371 } else { go=0 } } 372 } 373 return v 374} 375 376func xf_expr(g: *XfGrid, ip: *i64, e: i64) -> i64 { 377 var v: i64=xf_term(g, ip, e) 378 var go: i64=1 379 while go==1 { 380 if g.err != 0 { return 0 } 381 xf_ws(g.tsv, ip, e) 382 var op: i64=0 383 if ip[0]<e { op=g.tsv[ip[0]] as i64 } 384 if op==43 { 385 ip[0]=ip[0]+1 386 let r: i64=xf_term(g, ip, e) 387 v=v+r 388 } else { if op==45 { 389 ip[0]=ip[0]+1 390 let r2: i64=xf_term(g, ip, e) 391 v=v-r2 392 } else { go=0 } } 393 } 394 return v 395} 396 397func xf_evalcell(g: *XfGrid, r: i64, c: i64) -> i64 { 398 let idx: i64=r*XF_MAXC+c 399 g.gs[idx]=1 as u8 400 let ipb: *i64=sys_mmap(16) as *i64 401 ipb[0]=g.gfo[idx] 402 let e: i64=g.gfo[idx]+g.gfl[idx] 403 let v: i64=xf_expr(g, ipb, e) 404 if g.err==0 { xf_ws(g.tsv, ipb, e); if ipb[0] != e { g.err=1 } } 405 if g.err != 0 { return 0-1 } 406 g.gv[idx]=v 407 g.gs[idx]=2 as u8 408 return 0 409} 410 411// classify the TSV into the grid, then evaluate EVERY formula cell. 0 ok; nonzero = g.err (LOUD). 412func xf_load(g: *XfGrid, tsv: *u8, tn: i64) -> i64 { 413 var row: i64=0 414 var pos: i64=0 415 while pos<tn { 416 var le: i64=tn 417 var i: i64=pos 418 while i<tn { if tsv[i]==(10 as u8) { le=i; break } i=i+1 } 419 if le>pos { 420 var lineend: i64=le 421 if tsv[lineend-1]==(13 as u8) { lineend=lineend-1 } 422 var cs: i64=pos 423 var col: i64=0 424 var j: i64=pos 425 while j<=lineend { 426 var sep: i64=0 427 if j==lineend { sep=1 } else { if tsv[j]==(9 as u8) { sep=1 } } 428 if sep==1 { 429 if row<XF_MAXR { if col<XF_MAXC { 430 let idx: i64=row*XF_MAXC+col 431 let clen: i64=j-cs 432 if clen<=0 { g.gk[idx]=0 as u8 } else { 433 if tsv[cs]==(61 as u8) { 434 g.gk[idx]=3 as u8 435 g.gs[idx]=0 as u8 436 g.gfo[idx]=cs+1 437 g.gfl[idx]=clen-1 438 } else { 439 if xl_is_numeric(tsv, cs, clen)==1 { g.gk[idx]=1 as u8; g.gv[idx]=xf_q4_of(tsv, cs, clen) } else { g.gk[idx]=2 as u8 } 440 } 441 } 442 } } 443 col=col+1 444 cs=j+1 445 } 446 j=j+1 447 } 448 if col>g.ncols { g.ncols=col } 449 row=row+1 450 } 451 pos=le+1 452 } 453 g.nrows=row 454 // evaluate all formulas (memoized; chains resolve on demand; first error wins, attributed to its cell) 455 var r2: i64=0 456 while r2<g.nrows { 457 if r2>=XF_MAXR { break } 458 var c2: i64=0 459 while c2<g.ncols { 460 if c2>=XF_MAXC { break } 461 let idx2: i64=r2*XF_MAXC+c2 462 if (g.gk[idx2] as i64)==3 { if (g.gs[idx2] as i64)==0 { 463 g.errow=r2 464 g.ercol=c2 465 xf_evalcell(g, r2, c2) 466 if g.err != 0 { return g.err } 467 } } 468 c2=c2+1 469 } 470 r2=r2+1 471 } 472 return 0 473} 474 475// formula-aware sheet builder: '='-cells emit <f>FORMULA</f><v>computed</v>; all other cells take the 476// EXACT existing xl_emit_cell path (byte-identical output for formula-free workbooks). 477func xl_build_sheet_g(g: *XfGrid, tsv: *u8, tn: i64, out: *u8) -> i64 { 478 var o: i64=xl_cat(out,0,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><sheetData>" as *u8) 479 var rownum: i64=0 480 var pos: i64=0 481 while pos<tn { 482 var le: i64=tn 483 var i: i64=pos 484 while i<tn { if tsv[i]==(10 as u8) { le=i; break } i=i+1 } 485 if le>pos { 486 var lineend: i64=le 487 if tsv[lineend-1]==(13 as u8) { lineend=lineend-1 } 488 rownum=rownum+1 489 o=xl_cat(out,o,"<row r=\"" as *u8); o=xl_uint(out,o,rownum); o=xl_cat(out,o,"\">" as *u8) 490 var cs: i64=pos 491 var col: i64=0 492 var j: i64=pos 493 while j<=lineend { 494 var sep: i64=0 495 if j==lineend { sep=1 } else { if tsv[j]==(9 as u8) { sep=1 } } 496 if sep==1 { 497 var isform: i64=0 498 if rownum-1<XF_MAXR { if col<XF_MAXC { 499 let idx: i64=(rownum-1)*XF_MAXC+col 500 if (g.gk[idx] as i64)==3 { 501 isform=1 502 o=xl_cat(out,o,"<c r=\"" as *u8); o=xl_col(out,o,col); o=xl_uint(out,o,rownum); o=xl_cat(out,o,"\"><f>" as *u8) 503 o=xl_escape_region(out,o,tsv,g.gfo[idx],g.gfl[idx]) 504 o=xl_cat(out,o,"</f><v>" as *u8) 505 o=xf_fmtq4(out,o,g.gv[idx]) 506 o=xl_cat(out,o,"</v></c>" as *u8) 507 } 508 } } 509 if isform==0 { o=xl_emit_cell(out, o, col, rownum, tsv, cs, j-cs) } 510 col=col+1 511 cs=j+1 512 } 513 j=j+1 514 } 515 o=xl_cat(out,o,"</row>" as *u8) 516 } 517 pos=le+1 518 } 519 o=xl_cat(out,o,"</sheetData></worksheet>" as *u8) 520 return o 521} 522 523 524// ================= DIRECTIVES: CONDITIONAL FORMATTING / DATA VALIDATION / MACROS ================== 525// Both capabilities are DATA-DRIVEN: they are carried in the sheet source as '#' directive lines, so a 526// spreadsheet is still one plain TSV file and nothing needs a side-car format. 527// #CF <sqref> <op> <value> conditional formatting e.g. #CF B2:B4 greaterThan 100 528// #DV <sqref> <min> <max> data validation (whole, between) 529// #MACRO SET <row> <col> <value> write one cell 530// #MACRO FILL <r1> <c1> <r2> <c2> <value> 531// #MACRO CLEAR <row> <col> 532// 533// MACROS RUN BEFORE FORMULA EVALUATION, and that ordering is the whole point: a macro-written cell must 534// feed SUM, so the macro rewrites the SHEET SOURCE and the existing formula engine then loads the 535// already-macroed grid. Deliberately NOT Turing-complete -- no loops, no branches, no I/O, no exec. A 536// spreadsheet that can run arbitrary code is a malware format; this one can only move numbers it was 537// handed, which is the whole reason macro-enabled formats are a security disaster. 538const XL_MAXR: i64 = 512 539const XL_MAXC: i64 = 64 540const XL_CELLW: i64 = 40 541 542func xl_dir_is(buf: *u8, s: i64, e: i64, tag: *u8) -> i64 { 543 if s >= e { return 0 } 544 if buf[s] != (35 as u8) { return 0 } 545 var i: i64 = 0 546 while tag[i] != (0 as u8) { if s + i >= e { return 0 } if buf[s + i] != tag[i] { return 0 } i = i + 1 } 547 return 1 548} 549func xl_tok(buf: *u8, p: i64, e: i64, out: *u8, cap: i64, np: *i64) -> i64 { 550 var i: i64 = p 551 while i < e { if buf[i] != (32 as u8) { if buf[i] != (9 as u8) { break } } i = i + 1 } 552 var k: i64 = 0 553 while i < e { if buf[i] == (32 as u8) { break } if buf[i] == (9 as u8) { break } if k < cap - 1 { out[k] = buf[i]; k = k + 1 } i = i + 1 } 554 out[k] = 0 as u8 555 np[0] = i 556 return k 557} 558func xl_atoi_s(s: *u8) -> i64 { 559 var v: i64 = 0 560 var i: i64 = 0 561 while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } 562 return v 563} 564 565// Apply every #MACRO to the sheet source and emit a TSV with the directive lines REMOVED (a directive is 566// not a data row). Returns the new length. This is what runs before xf_grid_new/xf_load. 567func xl_macro_apply(tsv: *u8, n: i64, out: *u8, cap: i64) -> i64 { 568 let cells: *u8 = sys_mmap(XL_MAXR * XL_MAXC * XL_CELLW) 569 let used: *i64 = sys_mmap(XL_MAXR * 8) as *i64 570 var r: i64 = 0 571 while r < XL_MAXR { used[r] = 0; r = r + 1 } 572 var maxr: i64 = 0 573 574 // pass 1: load the data rows into the grid 575 var pos: i64 = 0 576 var row: i64 = 0 577 while pos < n { 578 var le: i64 = n 579 var i: i64 = pos 580 while i < n { if tsv[i] == (10 as u8) { le = i; break } i = i + 1 } 581 if le > pos { if tsv[pos] != (35 as u8) { 582 var col: i64 = 0 583 var cs: i64 = pos 584 var j: i64 = pos 585 while j <= le { 586 if j == le { 587 if col < XL_MAXC { if row < XL_MAXR { 588 var k: i64 = 0 589 while cs + k < j { if k < XL_CELLW - 1 { cells[(row * XL_MAXC + col) * XL_CELLW + k] = tsv[cs + k] } k = k + 1 } 590 cells[(row * XL_MAXC + col) * XL_CELLW + k] = 0 as u8 591 } } 592 col = col + 1 593 } else { if tsv[j] == (9 as u8) { 594 if col < XL_MAXC { if row < XL_MAXR { 595 var k2: i64 = 0 596 while cs + k2 < j { if k2 < XL_CELLW - 1 { cells[(row * XL_MAXC + col) * XL_CELLW + k2] = tsv[cs + k2] } k2 = k2 + 1 } 597 cells[(row * XL_MAXC + col) * XL_CELLW + k2] = 0 as u8 598 } } 599 col = col + 1 600 cs = j + 1 601 } } 602 j = j + 1 603 } 604 if row < XL_MAXR { used[row] = col } 605 if row + 1 > maxr { maxr = row + 1 } 606 row = row + 1 607 } } 608 pos = le + 1 609 } 610 611 // pass 2: apply the macros in source order 612 let t: *u8 = sys_mmap(64) 613 let np: *i64 = sys_mmap(16) as *i64 614 pos = 0 615 while pos < n { 616 var le2: i64 = n 617 var i2: i64 = pos 618 while i2 < n { if tsv[i2] == (10 as u8) { le2 = i2; break } i2 = i2 + 1 } 619 if xl_dir_is(tsv, pos, le2, "#MACRO" as *u8) == 1 { 620 var q: i64 = pos + 6 621 xl_tok(tsv, q, le2, t, 64, np) 622 q = np[0] 623 if xl_streq(t, "SET" as *u8) == 1 { 624 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let rr: i64 = xl_atoi_s(t) - 1 625 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let cc: i64 = xl_atoi_s(t) - 1 626 xl_tok(tsv, q, le2, t, 64, np); q = np[0] 627 if rr >= 0 { if cc >= 0 { if rr < XL_MAXR { if cc < XL_MAXC { 628 var k3: i64 = 0 629 while t[k3] != (0 as u8) { cells[(rr * XL_MAXC + cc) * XL_CELLW + k3] = t[k3]; k3 = k3 + 1 } 630 cells[(rr * XL_MAXC + cc) * XL_CELLW + k3] = 0 as u8 631 if used[rr] <= cc { used[rr] = cc + 1 } 632 if rr + 1 > maxr { maxr = rr + 1 } 633 } } } } 634 } 635 if xl_streq(t, "CLEAR" as *u8) == 1 { 636 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let rr2: i64 = xl_atoi_s(t) - 1 637 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let cc2: i64 = xl_atoi_s(t) - 1 638 if rr2 >= 0 { if cc2 >= 0 { if rr2 < XL_MAXR { if cc2 < XL_MAXC { cells[(rr2 * XL_MAXC + cc2) * XL_CELLW] = 0 as u8 } } } } 639 } 640 if xl_streq(t, "FILL" as *u8) == 1 { 641 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let r1: i64 = xl_atoi_s(t) - 1 642 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let c1: i64 = xl_atoi_s(t) - 1 643 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let r2: i64 = xl_atoi_s(t) - 1 644 xl_tok(tsv, q, le2, t, 64, np); q = np[0]; let c2: i64 = xl_atoi_s(t) - 1 645 xl_tok(tsv, q, le2, t, 64, np); q = np[0] 646 var rr3: i64 = r1 647 while rr3 <= r2 { 648 var cc3: i64 = c1 649 while cc3 <= c2 { 650 if rr3 >= 0 { if cc3 >= 0 { if rr3 < XL_MAXR { if cc3 < XL_MAXC { 651 var k4: i64 = 0 652 while t[k4] != (0 as u8) { cells[(rr3 * XL_MAXC + cc3) * XL_CELLW + k4] = t[k4]; k4 = k4 + 1 } 653 cells[(rr3 * XL_MAXC + cc3) * XL_CELLW + k4] = 0 as u8 654 if used[rr3] <= cc3 { used[rr3] = cc3 + 1 } 655 if rr3 + 1 > maxr { maxr = rr3 + 1 } 656 } } } } 657 cc3 = cc3 + 1 658 } 659 rr3 = rr3 + 1 660 } 661 } 662 } 663 pos = le2 + 1 664 } 665 666 // pass 3: re-emit the grid as TSV (directives dropped) 667 var o: i64 = 0 668 var rr4: i64 = 0 669 while rr4 < maxr { 670 var cc4: i64 = 0 671 let nc: i64 = used[rr4] 672 while cc4 < nc { 673 let base: i64 = (rr4 * XL_MAXC + cc4) * XL_CELLW 674 var k5: i64 = 0 675 while cells[base + k5] != (0 as u8) { if o < cap - 2 { out[o] = cells[base + k5]; o = o + 1 } k5 = k5 + 1 } 676 if cc4 + 1 < nc { if o < cap - 2 { out[o] = 9 as u8; o = o + 1 } } 677 cc4 = cc4 + 1 678 } 679 if o < cap - 2 { out[o] = 10 as u8; o = o + 1 } 680 rr4 = rr4 + 1 681 } 682 out[o] = 0 as u8 683 return o 684} 685 686// styles.xml carrying a real <dxfs> entry. THE cfRule BELOW POINTS AT dxfId 0, and a cfRule pointing at a 687// missing dxf is a HARD CORRUPT FILE in Excel -- so the style part is not optional decoration, it is what 688// makes the conditional-formatting claim true rather than merely emitted. 689func xl_styles_dxf() -> *u8 { 690 return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><fonts count=\"1\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts><fills count=\"2\"><fill><patternFill patternType=\"none\"/></fill><fill><patternFill patternType=\"gray125\"/></fill></fills><borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs><cellXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/></cellXfs><cellStyles count=\"1\"><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/></cellStyles><dxfs count=\"1\"><dxf><font><color rgb=\"FF9C0006\"/></font><fill><patternFill><bgColor rgb=\"FFFFC7CE\"/></patternFill></fill></dxf></dxfs><tableStyles count=\"0\"/></styleSheet>" as *u8 691} 692 693// Emit <conditionalFormatting> and <dataValidations> collected from the #CF / #DV directives. 694func xl_emit_cf_dv(out: *u8, off: i64, tsv: *u8, n: i64) -> i64 { 695 var o: i64 = off 696 let t: *u8 = sys_mmap(64) 697 let sq: *u8 = sys_mmap(64) 698 let np: *i64 = sys_mmap(16) as *i64 699 var pri: i64 = 1 700 var pos: i64 = 0 701 while pos < n { 702 var le: i64 = n 703 var i: i64 = pos 704 while i < n { if tsv[i] == (10 as u8) { le = i; break } i = i + 1 } 705 if xl_dir_is(tsv, pos, le, "#CF" as *u8) == 1 { 706 var q: i64 = pos + 3 707 xl_tok(tsv, q, le, sq, 64, np); q = np[0] 708 xl_tok(tsv, q, le, t, 64, np); q = np[0] 709 o = xl_cat(out, o, "<conditionalFormatting sqref=\"" as *u8) 710 o = xl_cat(out, o, sq) 711 o = xl_cat(out, o, "\"><cfRule type=\"cellIs\" dxfId=\"0\" priority=\"" as *u8) 712 o = xl_uint(out, o, pri) 713 o = xl_cat(out, o, "\" operator=\"" as *u8) 714 o = xl_cat(out, o, t) 715 o = xl_cat(out, o, "\"><formula>" as *u8) 716 xl_tok(tsv, q, le, t, 64, np); q = np[0] 717 o = xl_cat(out, o, t) 718 o = xl_cat(out, o, "</formula></cfRule></conditionalFormatting>" as *u8) 719 pri = pri + 1 720 } 721 pos = le + 1 722 } 723 // data validations must be wrapped in ONE <dataValidations> with a count 724 var ndv: i64 = 0 725 pos = 0 726 while pos < n { 727 var le3: i64 = n 728 var i3: i64 = pos 729 while i3 < n { if tsv[i3] == (10 as u8) { le3 = i3; break } i3 = i3 + 1 } 730 if xl_dir_is(tsv, pos, le3, "#DV" as *u8) == 1 { ndv = ndv + 1 } 731 pos = le3 + 1 732 } 733 if ndv > 0 { 734 o = xl_cat(out, o, "<dataValidations count=\"" as *u8) 735 o = xl_uint(out, o, ndv) 736 o = xl_cat(out, o, "\">" as *u8) 737 pos = 0 738 while pos < n { 739 var le4: i64 = n 740 var i4: i64 = pos 741 while i4 < n { if tsv[i4] == (10 as u8) { le4 = i4; break } i4 = i4 + 1 } 742 if xl_dir_is(tsv, pos, le4, "#DV" as *u8) == 1 { 743 var q2: i64 = pos + 3 744 xl_tok(tsv, q2, le4, sq, 64, np); q2 = np[0] 745 o = xl_cat(out, o, "<dataValidation type=\"whole\" operator=\"between\" allowBlank=\"1\" showInputMessage=\"1\" showErrorMessage=\"1\" sqref=\"" as *u8) 746 o = xl_cat(out, o, sq) 747 o = xl_cat(out, o, "\"><formula1>" as *u8) 748 xl_tok(tsv, q2, le4, t, 64, np); q2 = np[0] 749 o = xl_cat(out, o, t) 750 o = xl_cat(out, o, "</formula1><formula2>" as *u8) 751 xl_tok(tsv, q2, le4, t, 64, np); q2 = np[0] 752 o = xl_cat(out, o, t) 753 o = xl_cat(out, o, "</formula2></dataValidation>" as *u8) 754 } 755 pos = le4 + 1 756 } 757 o = xl_cat(out, o, "</dataValidations>" as *u8) 758 } 759 return o 760} 761 762func xlsx_write_tsv(tsvpath: *u8, outpath: *u8) -> i64 { 763 let szp: *i64=sys_mmap(16) as *i64 764 let tsv: *u8=sys_read_file(tsvpath, szp) 765 if (tsv as i64)==0 { return 0-1 } 766 // FORMULAS: classify + evaluate '='-cells before emitting (LOUD on syntax/bad-ref/cycle/div-0) 767 // MACROS FIRST: rewrite the sheet source so a macro-written cell is loaded as data and therefore 768 // feeds SUM. Running them after xf_load would make the macro invisible to every formula. 769 let mtsv: *u8=sys_mmap(XF_MAGIC_8388624) 770 var mlen: i64=xl_macro_apply(tsv, szp[0], mtsv, XF_MAGIC_8388624) 771 if mlen <= 0 { mlen = szp[0] } 772 let g: *XfGrid=xf_grid_new(mtsv) 773 let frc: i64=xf_load(g, mtsv, mlen) 774 if frc != 0 { 775 xl_puts("XLSX-FORMULA-FAIL kind=" as *u8); xl_num(frc) 776 xl_puts(" cell_row=" as *u8); xl_num(g.errow+1) 777 xl_puts(" cell_col=" as *u8); xl_num(g.ercol+1) 778 xl_puts(" (1=syntax 2=bad-ref/value 3=CYCLE 4=div-0)\n" as *u8) 779 return 0-9 780 } 781 let sheet: *u8=sys_mmap(XF_MAGIC_8388624) 782 var slen: i64=xl_build_sheet_g(g, mtsv, mlen, sheet) 783 // splice CF/DV between </sheetData> and </worksheet> (they are invalid anywhere else) 784 slen = slen - 12 785 slen = xl_emit_cf_dv(sheet, slen, tsv, szp[0]) 786 slen = xl_cat(sheet, slen, "</worksheet>" as *u8) 787 let ct: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"xml\" ContentType=\"application/xml\"/><Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/><Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/></Types>" as *u8 788 let rels: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/></Relationships>" as *u8 789 let wb: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"><sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets></workbook>" as *u8 790 let wbr: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/></Relationships>" as *u8 791 let names: *i64=sys_mmap(64) as *i64; let datap: *i64=sys_mmap(64) as *i64; let lens: *i64=sys_mmap(64) as *i64 792 names[0]="[Content_Types].xml" as *u8 as i64; datap[0]=ct as i64; lens[0]=xl_strlen(ct) 793 names[1]="_rels/.rels" as *u8 as i64; datap[1]=rels as i64; lens[1]=xl_strlen(rels) 794 names[2]="xl/workbook.xml" as *u8 as i64; datap[2]=wb as i64; lens[2]=xl_strlen(wb) 795 names[3]="xl/_rels/workbook.xml.rels" as *u8 as i64; datap[3]=wbr as i64; lens[3]=xl_strlen(wbr) 796 names[4]="xl/worksheets/sheet1.xml" as *u8 as i64; datap[4]=sheet as i64; lens[4]=slen 797 let sty: *u8=xl_styles_dxf() 798 names[5]="xl/styles.xml" as *u8 as i64; datap[5]=sty as i64; lens[5]=xl_strlen(sty) 799 return opc_write(outpath, names, datap, lens, 6) 800} 801 802// load xl/sharedStrings.xml (foreign Excel/Sheets files use t="s" cells indexing this table): each <si>'s 803// FIRST <t> text (entity-unescaped, NUL-terminated) into sbuf, offsets into soffs; returns the count. 804// Our own fromtsv files have no such part -> count 0, zero behavior change. (Rich <r>-run <si>s: first <t> only.) 805func xl_shared_load(path: *u8, sbuf: *u8, soffs: *i64, maxn: i64) -> i64 { 806 let sx: *u8=sys_mmap(XF_MAGIC_4194320) 807 let n: i64=opc_read_part(path, "xl/sharedStrings.xml" as *u8, sx, XF_MAGIC_4194304) 808 if n<0 { return 0 } 809 var cnt: i64=0; var i: i64=0; var o: i64=0 810 while i<n { 811 if xl_match(sx,i,n,"<si>" as *u8)==1 { 812 if cnt>=maxn { i=n } else { 813 soffs[cnt]=o 814 var k: i64=i+4 815 var havet: i64=0 816 while k<n { if xl_match(sx,k,n,"</si>" as *u8)==1 { break } if xl_match(sx,k,n,"<t" as *u8)==1 { havet=1; break } k=k+1 } 817 if havet==1 { 818 var selfc: i64=0 819 while k<n { if sx[k]==(47 as u8) { if sx[k+1]==(62 as u8) { selfc=1 } } if sx[k]==(62 as u8) { k=k+1; break } k=k+1 } 820 if selfc==0 { 821 while k<n { 822 if xl_match(sx,k,n,"</t>" as *u8)==1 { break } 823 if sx[k]==(38 as u8) { 824 if xl_match(sx,k,n,"&amp;" as *u8)==1 { sbuf[o]=38 as u8; o=o+1; k=k+5 } else { 825 if xl_match(sx,k,n,"&lt;" as *u8)==1 { sbuf[o]=60 as u8; o=o+1; k=k+4 } else { 826 if xl_match(sx,k,n,"&gt;" as *u8)==1 { sbuf[o]=62 as u8; o=o+1; k=k+4 } else { 827 if xl_match(sx,k,n,"&quot;" as *u8)==1 { sbuf[o]=34 as u8; o=o+1; k=k+6 } else { 828 if xl_match(sx,k,n,"&apos;" as *u8)==1 { sbuf[o]=39 as u8; o=o+1; k=k+6 } else { 829 sbuf[o]=sx[k]; o=o+1; k=k+1 } } } } } 830 } else { sbuf[o]=sx[k]; o=o+1; k=k+1 } 831 } 832 } 833 } 834 sbuf[o]=0 as u8; o=o+1 835 cnt=cnt+1 836 i=i+4 837 } 838 } else { i=i+1 } 839 } 840 return cnt 841} 842 843// read sheet1 grid back to TSV into out; return length (or <0). inlineStr + numeric <v> + SHARED (t="s", 844// resolved through xl_shared_load) all yield TEXT. 845func xlsx_read_tsv(path: *u8, out: *u8, cap: i64) -> i64 { 846 let sh: *u8=sys_mmap(XF_MAGIC_8388624) 847 let n: i64=opc_read_part(path, "xl/worksheets/sheet1.xml" as *u8, sh, XF_MAGIC_8388608) 848 if n<0 { return n } 849 let sstr: *u8=sys_mmap(XF_MAGIC_1048592) 850 let soffs: *i64=sys_mmap(8*XF_MAGIC_16384) as *i64 851 let scount: i64=xl_shared_load(path, sstr, soffs, XF_MAGIC_16384) 852 var o: i64=0; var i: i64=0; var firstrow: i64=1; var cellcol: i64=0 853 while i<n { 854 if xl_match(sh,i,n,"</row>" as *u8)==1 { i=i+6 } else { 855 if xl_match(sh,i,n,"<row" as *u8)==1 { 856 if firstrow==0 { out[o]=10 as u8; o=o+1 } 857 firstrow=0; cellcol=0 858 while i<n { if sh[i]==(62 as u8) { i=i+1; break } i=i+1 } 859 } else { 860 if xl_match(sh,i,n,"<c" as *u8)==1 { 861 let c2: i64=sh[i+2] as i64 862 var iscell: i64=0 863 if c2==32 { iscell=1 } else { if c2==62 { iscell=1 } else { if c2==47 { iscell=1 } } } 864 if iscell==1 { 865 if cellcol>0 { out[o]=9 as u8; o=o+1 } 866 cellcol=cellcol+1 867 var isStr: i64=0; var isShared: i64=0; var selfclose: i64=0; var j: i64=i+2 868 while j<n { if sh[j]==(62 as u8) { break } if xl_match(sh,j,n,"t=\"inlineStr\"" as *u8)==1 { isStr=1 } if xl_match(sh,j,n,"t=\"s\"" as *u8)==1 { isShared=1 } if sh[j]==(47 as u8) { if sh[j+1]==(62 as u8) { selfclose=1 } } j=j+1 } 869 if selfclose==1 { i=j+1 } else { 870 if isStr==1 { 871 var k: i64=j 872 while k<n { if xl_match(sh,k,n,"<t" as *u8)==1 { break } k=k+1 } 873 while k<n { if sh[k]==(62 as u8) { k=k+1; break } k=k+1 } // past <t...> 874 while k<n { 875 if xl_match(sh,k,n,"</t>" as *u8)==1 { break } 876 if sh[k]==(38 as u8) { 877 if xl_match(sh,k,n,"&amp;" as *u8)==1 { out[o]=38 as u8; o=o+1; k=k+5 } else { 878 if xl_match(sh,k,n,"&lt;" as *u8)==1 { out[o]=60 as u8; o=o+1; k=k+4 } else { 879 if xl_match(sh,k,n,"&gt;" as *u8)==1 { out[o]=62 as u8; o=o+1; k=k+4 } else { 880 if xl_match(sh,k,n,"&quot;" as *u8)==1 { out[o]=34 as u8; o=o+1; k=k+6 } else { 881 if xl_match(sh,k,n,"&apos;" as *u8)==1 { out[o]=39 as u8; o=o+1; k=k+6 } else { 882 out[o]=sh[k]; o=o+1; k=k+1 } } } } } 883 } else { out[o]=sh[k]; o=o+1; k=k+1 } 884 } 885 while k<n { if xl_match(sh,k,n,"</c>" as *u8)==1 { break } k=k+1 } 886 i=k+4 887 } else { 888 if isShared==1 { 889 // t="s": <v> holds an INDEX into the sharedStrings table -> emit the resolved text 890 var k: i64=j 891 while k<n { if xl_match(sh,k,n,"<v>" as *u8)==1 { break } k=k+1 } 892 k=k+3 893 var idxv: i64=0 894 while k<n { if xl_match(sh,k,n,"</v>" as *u8)==1 { break } let dc: i64=sh[k] as i64; if dc>=48 { if dc<=57 { idxv=idxv*10+(dc-48) } } k=k+1 } 895 if idxv<scount { o=xl_cat(out, o, ((sstr as i64) + soffs[idxv]) as *u8) } 896 while k<n { if xl_match(sh,k,n,"</c>" as *u8)==1 { break } k=k+1 } 897 i=k+4 898 } else { 899 var k: i64=j 900 while k<n { if xl_match(sh,k,n,"<v>" as *u8)==1 { break } k=k+1 } 901 k=k+3 902 while k<n { if xl_match(sh,k,n,"</v>" as *u8)==1 { break } out[o]=sh[k]; o=o+1; k=k+1 } 903 while k<n { if xl_match(sh,k,n,"</c>" as *u8)==1 { break } k=k+1 } 904 i=k+4 905 } 906 } 907 } 908 } else { i=i+1 } 909 } else { i=i+1 } } } 910 } 911 out[o]=0 as u8 912 return o 913} 914 915// emit one char HTML-escaped into dst 916func xl_esc1(dst: *u8, o: i64, c: i64) -> i64 { 917 if c==38 { return xl_cat(dst,o,"&amp;" as *u8) } 918 if c==60 { return xl_cat(dst,o,"&lt;" as *u8) } 919 if c==62 { return xl_cat(dst,o,"&gt;" as *u8) } 920 dst[o]=c as u8; return o+1 921} 922 923// one TSV row tsv[s,e) -> <tr> with TAB-split th/td cells 924func xl_rowhtml(out: *u8, off: i64, tsv: *u8, s: i64, e: i64, ishdr: i64) -> i64 { 925 var o: i64=off 926 o=xl_cat(out,o,"<tr>" as *u8) 927 var p: i64=s 928 var cs: i64=s 929 while p<=e { 930 var attab: i64=0 931 if p==e { attab=1 } else { if tsv[p]==(9 as u8) { attab=1 } } 932 if attab==1 { 933 if ishdr==1 { o=xl_cat(out,o,"<th>" as *u8) } else { o=xl_cat(out,o,"<td>" as *u8) } 934 var q: i64=cs 935 while q<p { o=xl_esc1(out,o,tsv[q] as i64); q=q+1 } 936 if ishdr==1 { o=xl_cat(out,o,"</th>" as *u8) } else { o=xl_cat(out,o,"</td>" as *u8) } 937 cs=p+1 938 } 939 p=p+1 940 } 941 o=xl_cat(out,o,"</tr>\n" as *u8) 942 return o 943} 944 945// CHARTS v1 (OF-S4, native-first): render a 0-JS BAR CHART from the sheet's data -- labels = column A, 946// values = the first column whose data cells are >=60% numeric (formula cells count: read resolves them to 947// computed values first). Bar widths are integer percents of the max (style attr, no JS). Sheets with no 948// numeric column get no chart section (graceful). In-FILE DrawingML c:chart parts (Excel-native charts) are 949// the interop second half -- a NAMED follow-up, not claimed. 950func xl_chart_html(out: *u8, off: i64, tsv: *u8, tn: i64) -> i64 { 951 var o: i64=off 952 // collect row spans (up to 25: header + 24 bars) 953 let rs: *i64=sys_mmap(8*64) as *i64 954 let re: *i64=sys_mmap(8*64) as *i64 955 var nr: i64=0 956 var i: i64=0 957 while i<tn { 958 var e: i64=i 959 while e<tn { if tsv[e]==(10 as u8) { break } e=e+1 } 960 if e>i { if nr<40 { rs[nr]=i; re[nr]=e; nr=nr+1 } } 961 i=e+1 962 } 963 if nr<2 { return o } 964 // find the value column: first col c>=1 whose data cells are >=60% numeric (and >0 numeric) 965 var vc: i64=0-1 966 var c: i64=1 967 while c<16 { 968 if vc>=0 { c=16 } else { 969 var num: i64=0 970 var tot: i64=0 971 var r: i64=1 972 while r<nr { 973 // cell (r, c) 974 var p: i64=rs[r] 975 var cc: i64=0 976 var cs2: i64=p 977 var fend: i64=0-1 978 var fstart: i64=0-1 979 while p<=re[r] { 980 var sep: i64=0 981 if p==re[r] { sep=1 } else { if tsv[p]==(9 as u8) { sep=1 } } 982 if sep==1 { if cc==c { fstart=cs2; fend=p; p=re[r]+1 } else { cc=cc+1; cs2=p+1 } } 983 p=p+1 984 } 985 if fstart>=0 { if fend>fstart { 986 tot=tot+1 987 if xl_is_numeric(tsv, fstart, fend-fstart)==1 { num=num+1 } 988 } } 989 r=r+1 990 } 991 if num>0 { if num*10 >= tot*6 { vc=c } } 992 if vc<0 { c=c+1 } 993 } 994 } 995 if vc<0 { return o } 996 // max value (Q4) 997 var mx: i64=0 998 var r2: i64=1 999 while r2<nr { 1000 var p2: i64=rs[r2] 1001 var cc2: i64=0 1002 var cs3: i64=p2 1003 while p2<=re[r2] { 1004 var sep2: i64=0 1005 if p2==re[r2] { sep2=1 } else { if tsv[p2]==(9 as u8) { sep2=1 } } 1006 if sep2==1 { 1007 if cc2==vc { if p2>cs3 { if xl_is_numeric(tsv,cs3,p2-cs3)==1 { let v: i64=xf_q4_of(tsv,cs3,p2-cs3); if v>mx { mx=v } } } } 1008 cc2=cc2+1 1009 cs3=p2+1 1010 } 1011 p2=p2+1 1012 } 1013 r2=r2+1 1014 } 1015 if mx<=0 { return o } 1016 o=xl_cat(out,o,"<div class=chart><p class=ct>Chart &mdash; first numeric column, bars sized to the max value</p>\n" as *u8) 1017 var r3: i64=1 1018 var bars: i64=0 1019 while r3<nr { 1020 if bars<24 { 1021 // label = col 0, value = col vc 1022 var p3: i64=rs[r3] 1023 var cc3: i64=0 1024 var cs4: i64=p3 1025 var lab_s: i64=0-1 1026 var lab_e: i64=0-1 1027 var val_s: i64=0-1 1028 var val_e: i64=0-1 1029 while p3<=re[r3] { 1030 var sep3: i64=0 1031 if p3==re[r3] { sep3=1 } else { if tsv[p3]==(9 as u8) { sep3=1 } } 1032 if sep3==1 { 1033 if cc3==0 { lab_s=cs4; lab_e=p3 } 1034 if cc3==vc { val_s=cs4; val_e=p3 } 1035 cc3=cc3+1 1036 cs4=p3+1 1037 } 1038 p3=p3+1 1039 } 1040 if val_s>=0 { if val_e>val_s { if xl_is_numeric(tsv,val_s,val_e-val_s)==1 { 1041 let v2: i64=xf_q4_of(tsv,val_s,val_e-val_s) 1042 if v2>=0 { 1043 var pct: i64=(v2*100)/mx 1044 if pct<1 { pct=1 } 1045 o=xl_cat(out,o,"<div class=brow><span class=bl>" as *u8) 1046 var q2: i64=lab_s 1047 while q2<lab_e { o=xl_esc1(out,o,tsv[q2] as i64); q2=q2+1 } 1048 o=xl_cat(out,o,"</span><div class=bar style=\"width:" as *u8) 1049 o=xl_uint(out,o,pct) 1050 o=xl_cat(out,o,"%\">" as *u8) 1051 var q3: i64=val_s 1052 while q3<val_e { o=xl_esc1(out,o,tsv[q3] as i64); q3=q3+1 } 1053 o=xl_cat(out,o,"</div></div>\n" as *u8) 1054 bars=bars+1 1055 } 1056 } } } 1057 } 1058 r3=r3+1 1059 } 1060 o=xl_cat(out,o,"</div>\n" as *u8) 1061 return o 1062} 1063 1064// shared TSV -> standard 0-JS preview page (auto-chart + table). label = the small caption over the data. 1065// Used by BOTH the sheet preview (xlsx_to_html) and the PIVOT view (rule 15 DRY). 1066func xl_tsv_to_html(tsv: *u8, tn: i64, out: *u8, dlurl: *u8, label: *u8) -> i64 { 1067 var o: i64=0 1068 o=xl_cat(out,o,"<!DOCTYPE html>\n<html lang=en><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Document Preview - Nishi Office</title><style>\nbody{font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#eef1f5;color:#1a1d21}\nheader{background:#0f172a;color:#fff;padding:16px 24px}header h1{margin:0;font-size:17px;font-weight:600}\n.doc{max-width:960px;margin:22px auto;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:24px;box-shadow:0 1px 4px rgba(15,23,42,.08);overflow-x:auto}\n.sheet{font-size:13px;color:#64748b;margin:0 0 10px}\ntable{border-collapse:collapse;width:100%;font-size:14px}\nth,td{border:1px solid #cbd5e1;padding:7px 12px;text-align:left}\nth{background:#0f172a;color:#fff;position:sticky;top:0}\ntr:nth-child(even) td{background:#f8fafc}\n.chart{margin:0 0 18px}.ct{font-size:13px;color:#64748b;margin:0 0 8px}\n.brow{display:flex;align-items:center;margin:4px 0}.bl{width:220px;font-size:13px;color:#334155;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;flex-shrink:0}\n.bar{background:#2563eb;color:#fff;font-size:12px;border-radius:4px;padding:3px 8px;min-width:14px}\n.dlbar{max-width:960px;margin:0 auto 6px;text-align:right}.dl{display:inline-block;padding:9px 18px;background:#2563eb;color:#fff;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500}\nfooter{max-width:960px;margin:14px auto 46px;color:#64748b;font-size:13px;text-align:center}\n</style></head>\n<body>\n<header><h1>&#128202; Document Preview &mdash; Nishi Office</h1></header>\n<div class=doc>\n<p class=sheet>" as *u8) 1069 o=xl_cat(out,o,label) 1070 o=xl_cat(out,o,"</p>\n" as *u8) 1071 o=xl_chart_html(out, o, tsv, tn) 1072 o=xl_cat(out,o,"<table>\n" as *u8) 1073 var i: i64=0; var first: i64=1 1074 while i<tn { 1075 var e: i64=i 1076 while e<tn { if tsv[e]==(10 as u8) { break } e=e+1 } 1077 if e>i { 1078 if first==1 { o=xl_rowhtml(out,o,tsv,i,e,1); first=0 } else { o=xl_rowhtml(out,o,tsv,i,e,0) } 1079 } 1080 i=e+1 1081 } 1082 o=xl_cat(out,o,"</table>\n</div>\n" as *u8) 1083 if (dlurl as i64)!=0 { o=xl_cat(out,o,"<div class=dlbar><a class=dl href=\"" as *u8); o=xl_cat(out,o,dlurl); o=xl_cat(out,o,"\">&#11015; Download .xlsx (opens in Excel / Sheets / Numbers)</a></div>\n" as *u8) } 1084 o=xl_cat(out,o,"<footer>Rendered natively from SpreadsheetML by <b>nx_xlsx</b> &middot; viewable inline in the Nishi browser &middot; your workbooks stay on your hardware</footer>\n</body></html>\n" as *u8) 1085 return o 1086} 1087 1088// .xlsx -> the standard 0-JS inline preview (sheet1 as a table; first row = header). Composes 1089// xlsx_read_tsv, so inlineStr + numeric + SHARED strings all render as text. Same iface as nx_docx/nx_md/ 1090// nx_csv/nx_pdf so the nx_doc_view registry (and thus the Nishi browser) routes .xlsx with zero new code. 1091func xlsx_to_html(path: *u8, out: *u8, dlurl: *u8) -> i64 { 1092 let tsv: *u8=sys_mmap(XF_MAGIC_8388624) 1093 let tn: i64=xlsx_read_tsv(path, tsv, XF_MAGIC_8388608) 1094 if tn<0 { return tn } 1095 return xl_tsv_to_html(tsv, tn, out, dlurl, "Sheet1" as *u8) 1096} 1097 1098// ---- PIVOT v1 (OF-S5, native-first): GROUP-BY aggregation over the resolved sheet (formula cells count at 1099// their computed values). Groups by column G (letter), aggregates column V: SUM / COUNT / AVG (Q4; AVG floors) 1100// + a TOTAL row. Data rows = 2..N (row 1 = header); a row participates when its group cell is non-empty AND 1101// its value cell is numeric. HONEST scope: single group column, three aggregates; in-FILE pivotTable parts + 1102// drag-drop pivot UI = named follow-ups. Intended for TRANSACTIONAL sheets (summary rows form literal groups). 1103func xl_colletter(s: *u8) -> i64 { 1104 var col: i64=0 1105 var i: i64=0 1106 while s[i]!=(0 as u8) { 1107 let c: i64=s[i] as i64 1108 if c>=65 { if c<=90 { col=col*26+(c-64); i=i+1 } else { return 0-1 } } else { return 0-1 } 1109 if i>2 { return 0-1 } 1110 } 1111 if col<1 { return 0-1 } 1112 return col-1 1113} 1114 1115// pivot the TSV: group col gc, value col vc (0-based) -> pivot TSV in pout. Returns length (or <0: -1 = no 1116// participating rows). 1117func xl_pivot(tsv: *u8, tn: i64, gc: i64, vc: i64, pout: *u8) -> i64 { 1118 // group registry (cap 128): span into tsv + sum/count 1119 let gso: *i64=sys_mmap(8*128) as *i64 1120 let gsl: *i64=sys_mmap(8*128) as *i64 1121 let gsum: *i64=sys_mmap(8*128) as *i64 1122 let gcnt: *i64=sys_mmap(8*128) as *i64 1123 var ng: i64=0 1124 var row: i64=0 1125 var i: i64=0 1126 var total: i64=0 1127 var totcnt: i64=0 1128 while i<tn { 1129 var e: i64=i 1130 while e<tn { if tsv[e]==(10 as u8) { break } e=e+1 } 1131 if e>i { 1132 row=row+1 1133 if row>=2 { 1134 // locate group + value cells 1135 var p: i64=i 1136 var cc: i64=0 1137 var cs: i64=i 1138 var g_s: i64=0-1 1139 var g_e: i64=0-1 1140 var v_s: i64=0-1 1141 var v_e: i64=0-1 1142 while p<=e { 1143 var sep: i64=0 1144 if p==e { sep=1 } else { if tsv[p]==(9 as u8) { sep=1 } } 1145 if sep==1 { 1146 if cc==gc { g_s=cs; g_e=p } 1147 if cc==vc { v_s=cs; v_e=p } 1148 cc=cc+1 1149 cs=p+1 1150 } 1151 p=p+1 1152 } 1153 if g_s>=0 { if g_e>g_s { if v_s>=0 { if v_e>v_s { if xl_is_numeric(tsv,v_s,v_e-v_s)==1 { 1154 let v: i64=xf_q4_of(tsv,v_s,v_e-v_s) 1155 // find/insert group 1156 var gi: i64=0-1 1157 var k: i64=0 1158 while k<ng { 1159 if gsl[k]==g_e-g_s { 1160 var same: i64=1 1161 var q: i64=0 1162 while q<gsl[k] { if tsv[gso[k]+q]!=tsv[g_s+q] { same=0; q=gsl[k] } else { q=q+1 } } 1163 if same==1 { gi=k; k=ng } 1164 } 1165 if gi<0 { k=k+1 } 1166 } 1167 if gi<0 { if ng<128 { gso[ng]=g_s; gsl[ng]=g_e-g_s; gsum[ng]=0; gcnt[ng]=0; gi=ng; ng=ng+1 } } 1168 if gi>=0 { 1169 gsum[gi]=gsum[gi]+v 1170 gcnt[gi]=gcnt[gi]+1 1171 total=total+v 1172 totcnt=totcnt+1 1173 } 1174 } } } } } 1175 } 1176 } 1177 i=e+1 1178 } 1179 if totcnt==0 { return 0-1 } 1180 var o: i64=0 1181 o=xl_cat(pout,o,"Group\x09Sum\x09Count\x09Avg\n" as *u8) 1182 var g2: i64=0 1183 while g2<ng { 1184 var q2: i64=0 1185 while q2<gsl[g2] { pout[o]=tsv[gso[g2]+q2]; o=o+1; q2=q2+1 } 1186 pout[o]=9 as u8 1187 o=o+1 1188 o=xf_fmtq4(pout,o,gsum[g2]) 1189 pout[o]=9 as u8 1190 o=o+1 1191 o=xl_uint(pout,o,gcnt[g2]) 1192 pout[o]=9 as u8 1193 o=o+1 1194 o=xf_fmtq4(pout,o,gsum[g2]/gcnt[g2]) 1195 pout[o]=10 as u8 1196 o=o+1 1197 g2=g2+1 1198 } 1199 o=xl_cat(pout,o,"TOTAL\x09" as *u8) 1200 o=xf_fmtq4(pout,o,total) 1201 pout[o]=9 as u8 1202 o=o+1 1203 o=xl_uint(pout,o,totcnt) 1204 pout[o]=9 as u8 1205 o=o+1 1206 o=xf_fmtq4(pout,o,total/totcnt) 1207 pout[o]=10 as u8 1208 o=o+1 1209 return o 1210} 1211 1212func main(argc: i64, argv: *i64) -> i64 { 1213 if argc<3 { xl_puts("usage: nx_xlsx fromtsv <in.tsv> <out.xlsx> | read <in.xlsx> | html <in.xlsx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 } 1214 let cmd: *u8=argv[1] as *u8 1215 if xl_streq(cmd,"fromtsv" as *u8)==1 { 1216 if argc<4 { xl_puts("usage: nx_xlsx fromtsv <in.tsv> <out.xlsx>\n" as *u8); sys_exit(2); return 2 } 1217 let sz: i64=xlsx_write_tsv(argv[2] as *u8, argv[3] as *u8) 1218 if sz<0 { xl_puts("XLSX-WRITE-FAIL code=" as *u8); xl_num(sz); xl_puts("\n" as *u8); sys_exit(1); return 1 } 1219 xl_puts("XLSX-WRITE-OK path=" as *u8); xl_puts(argv[3] as *u8); xl_puts(" bytes=" as *u8); xl_num(sz); xl_puts("\n" as *u8) 1220 sys_exit(0); return 0 1221 } 1222 if xl_streq(cmd,"read" as *u8)==1 { 1223 let out: *u8=sys_mmap(XF_MAGIC_8388624) 1224 let tl: i64=xlsx_read_tsv(argv[2] as *u8, out, XF_MAGIC_8388608) 1225 if tl<0 { xl_puts("XLSX-READ-FAIL code=" as *u8); xl_num(tl); xl_puts("\n" as *u8); sys_exit(1); return 1 } 1226 xl_puts("XLSX-GRID-BEGIN\n" as *u8); sys_write(1, out, tl); xl_puts("\nXLSX-GRID-END\n" as *u8) 1227 sys_exit(0); return 0 1228 } 1229 if xl_streq(cmd,"html" as *u8)==1 { 1230 if argc<4 { xl_puts("usage: nx_xlsx html <in.xlsx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 } 1231 var dlu: *u8=0 as *u8 1232 if argc>4 { dlu=argv[4] as *u8 } 1233 let out: *u8=sys_mmap(XF_MAGIC_33554432) 1234 let hn: i64=xlsx_to_html(argv[2] as *u8, out, dlu) 1235 if hn<0 { xl_puts("XLSX-HTML-FAIL code=" as *u8); xl_num(hn); xl_puts("\n" as *u8); sys_exit(1); return 1 } 1236 let fd: i64=sys_openat_wr(argv[3] as *u8, 0x1a4) 1237 if fd<0 { xl_puts("XLSX-HTML-FAIL cannot-write\n" as *u8); sys_exit(1); return 1 } 1238 sys_write(fd, out, hn); sys_close(fd) 1239 xl_puts("XLSX-HTML-OK path=" as *u8); xl_puts(argv[3] as *u8); xl_puts(" bytes=" as *u8); xl_num(hn); xl_puts("\n" as *u8) 1240 sys_exit(0); return 0 1241 } 1242 if xl_streq(cmd,"pivot" as *u8)==1 { 1243 if argc<5 { xl_puts("usage: nx_xlsx pivot <in.xlsx> <groupcol> <valuecol> (columns as letters, e.g. B C)\n" as *u8); sys_exit(2); return 2 } 1244 let gc: i64=xl_colletter(argv[3] as *u8) 1245 let vc: i64=xl_colletter(argv[4] as *u8) 1246 if gc<0 { xl_puts("XLSX-PIVOT-FAIL bad-group-column\n" as *u8); sys_exit(1); return 1 } 1247 if vc<0 { xl_puts("XLSX-PIVOT-FAIL bad-value-column\n" as *u8); sys_exit(1); return 1 } 1248 let tsv: *u8=sys_mmap(XF_MAGIC_8388624) 1249 let tn: i64=xlsx_read_tsv(argv[2] as *u8, tsv, XF_MAGIC_8388608) 1250 if tn<0 { xl_puts("XLSX-PIVOT-FAIL cannot-read code=" as *u8); xl_num(tn); xl_puts("\n" as *u8); sys_exit(1); return 1 } 1251 let pv: *u8=sys_mmap(XF_MAGIC_1048576) 1252 let pn: i64=xl_pivot(tsv, tn, gc, vc, pv) 1253 if pn<0 { xl_puts("XLSX-PIVOT-FAIL no-numeric-rows\n" as *u8); sys_exit(1); return 1 } 1254 xl_puts("XLSX-PIVOT-BEGIN\n" as *u8) 1255 sys_write(1, pv, pn) 1256 xl_puts("XLSX-PIVOT-END\n" as *u8) 1257 sys_exit(0); return 0 1258 } 1259 if xl_streq(cmd,"pivothtml" as *u8)==1 { 1260 if argc<6 { xl_puts("usage: nx_xlsx pivothtml <in.xlsx> <groupcol> <valuecol> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 } 1261 let gc: i64=xl_colletter(argv[3] as *u8) 1262 let vc: i64=xl_colletter(argv[4] as *u8) 1263 if gc<0 { xl_puts("XLSX-PIVOT-FAIL bad-group-column\n" as *u8); sys_exit(1); return 1 } 1264 if vc<0 { xl_puts("XLSX-PIVOT-FAIL bad-value-column\n" as *u8); sys_exit(1); return 1 } 1265 let tsv: *u8=sys_mmap(XF_MAGIC_8388624) 1266 let tn: i64=xlsx_read_tsv(argv[2] as *u8, tsv, XF_MAGIC_8388608) 1267 if tn<0 { xl_puts("XLSX-PIVOT-FAIL cannot-read code=" as *u8); xl_num(tn); xl_puts("\n" as *u8); sys_exit(1); return 1 } 1268 let pv: *u8=sys_mmap(XF_MAGIC_1048576) 1269 let pn: i64=xl_pivot(tsv, tn, gc, vc, pv) 1270 if pn<0 { xl_puts("XLSX-PIVOT-FAIL no-numeric-rows\n" as *u8); sys_exit(1); return 1 } 1271 var dlu: *u8=0 as *u8 1272 if argc>6 { dlu=argv[6] as *u8 } 1273 let out: *u8=sys_mmap(XF_MAGIC_33554432) 1274 let hn: i64=xl_tsv_to_html(pv, pn, out, dlu, "Pivot (group-by with SUM / COUNT / AVG; chart = Sum per group)" as *u8) 1275 let fd: i64=sys_openat_wr(argv[5] as *u8, 0x1a4) 1276 if fd<0 { xl_puts("XLSX-PIVOT-FAIL cannot-write\n" as *u8); sys_exit(1); return 1 } 1277 sys_write(fd, out, hn); sys_close(fd) 1278 xl_puts("XLSX-PIVOT-HTML-OK path=" as *u8); xl_puts(argv[5] as *u8); xl_puts(" bytes=" as *u8); xl_num(hn); xl_puts("\n" as *u8) 1279 sys_exit(0); return 0 1280 } 1281 xl_puts("nx_xlsx: unknown command (fromtsv|read|html|pivot|pivothtml)\n" as *u8) 1282 sys_exit(2); return 2 1283}