code wiki / _hdl_build / nx_docx.nx

nx_docx.nx source

↩ module page · 670 lines · 42544 B

1// nx_docx.nx -- SOVEREIGN MS-Word (.docx / OOXML) interop for the Nishi Office suite. A .docx is an OPC package 2// = a ZIP of XML parts (framed by the shared nx_opc). This organ READS and WRITES real .docx with ZERO 3rd-party 3// code: 4// write : paragraphs -> a minimal valid OPC package ([Content_Types].xml + _rels/.rels + word/document.xml) 5// -> a file Microsoft Word / unzip can open. 6// read : a .docx -> the text of word/document.xml (STORED and DEFLATE entries both handled via nx_opc), 7// extracting each <w:t> run and joining runs with newlines, unescaping XML entities. 8// This is the "lawyers make native edits / interop with Microsoft Office" keystone; nx_mailmerge composes on top. 9// Sovereignty = our own ZIP+OOXML; rigor (non-novel-format doctrine) = the harness ALSO proves a 3rd-party 10// unzip/zipfile opens what we write and that we read what it writes. 11// nx_docx write <path.docx> <paragraph1> [paragraph2 ...] 12// nx_docx read <path.docx> 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 16import "nx_opc.nx" 17const K_MAGIC_1048592: i64 = 1048592 18const K_MAGIC_1048576: i64 = 1048576 19const K_MAGIC_262160: i64 = 262160 20const K_MAGIC_2097168: i64 = 2097168 21const K_MAGIC_2097152: i64 = 2097152 22 23func dx_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 24// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 25// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 26// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 27// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 28func dx_num(v: i64) -> i64 { nxi_out(v); return 0 } 29func dx_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 30func dx_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 } 31func dx_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 } 32 33// pat (NUL-terminated) matches buf at pos, within [pos,n)? 34func dx_match(buf: *u8, pos: i64, n: i64, pat: *u8) -> i64 { 35 var k: i64=0 36 while pat[k]!=(0 as u8) { if pos+k>=n { return 0 } if buf[pos+k]!=pat[k] { return 0 } k=k+1 } 37 return 1 38} 39// copy s into dst escaping XML & < > ; return new offset 40func dx_xml_escape(dst: *u8, off: i64, s: *u8) -> i64 { 41 var o: i64=off; var i: i64=0 42 while s[i]!=(0 as u8) { 43 let c: i64=s[i] as i64 44 if c==38 { o=dx_cat(dst,o,"&amp;" as *u8) } else { if c==60 { o=dx_cat(dst,o,"&lt;" as *u8) } else { if c==62 { o=dx_cat(dst,o,"&gt;" as *u8) } else { dst[o]=s[i]; o=o+1 } } } 45 i=i+1 46 } 47 return o 48} 49 50// build word/document.xml from nparas paragraphs into out; return length 51func dx_build_document(paras: *i64, nparas: i64, out: *u8) -> i64 { 52 var o: i64=0 53 o=dx_cat(out, o, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:body>" as *u8) 54 var i: i64=0 55 while i<nparas { 56 o=dx_cat(out, o, "<w:p><w:r><w:t xml:space=\"preserve\">" as *u8) 57 o=dx_xml_escape(out, o, paras[i] as *u8) 58 o=dx_cat(out, o, "</w:t></w:r></w:p>" as *u8) 59 i=i+1 60 } 61 o=dx_cat(out, o, "</w:body></w:document>" as *u8) 62 return o 63} 64 65// write a .docx whose word/document.xml is the given raw bytes (+ fixed Content_Types/.rels); return total bytes 66func docx_write_document_xml(path: *u8, doc: *u8, doclen: i64) -> i64 { 67 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=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/></Types>" as *u8 68 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=\"word/document.xml\"/></Relationships>" as *u8 69 let names: *i64=sys_mmap(64) as *i64 70 let datap: *i64=sys_mmap(64) as *i64 71 let lens: *i64=sys_mmap(64) as *i64 72 names[0]="[Content_Types].xml" as *u8 as i64; datap[0]=ct as i64; lens[0]=dx_strlen(ct) 73 names[1]="_rels/.rels" as *u8 as i64; datap[1]=rels as i64; lens[1]=dx_strlen(rels) 74 names[2]="word/document.xml" as *u8 as i64; datap[2]=doc as i64; lens[2]=doclen 75 return opc_write(path, names, datap, lens, 3) 76} 77 78// write a .docx from paragraph strings (builds document.xml, then frames the OPC ZIP) 79func docx_write(path: *u8, paras: *i64, nparas: i64) -> i64 { 80 let doc: *u8=sys_mmap(K_MAGIC_1048592) 81 let doclen: i64=dx_build_document(paras, nparas, doc) 82 return docx_write_document_xml(path, doc, doclen) 83} 84 85 86// ================= OOXML FEATURE EMITTERS ========================================================= 87// The office compare-matrix claimed five DOCX capabilities whose emitters did not exist anywhere in the 88// tree (dx_emit_drawing / sectPr / dx_emit_track / dx_cm_add / dx_fn_add): the axes read PRESENT while 89// nothing implemented them. Implemented here for real rather than withdrawing the claims -- and note the 90// grounding ruler only counts SYMBOL PRESENCE, so a stub would have "grounded" them while proving 91// nothing. Each emitter below produces genuine WordprocessingML and is exercised by the `feat` verb, 92// which writes a package a third-party unzip can open (the organ's own non-novel-format doctrine). 93const DX_EMU_PER_PX: i64 = 9525 // 914400 EMU/inch / 96 px/inch (ECMA-376 drawing units) 94 95// append a decimal integer into a buffer (dx_num prints to stdout; emitters need it IN the XML) 96func dx_catn(dst: *u8, off: i64, v: i64) -> i64 { 97 if v == 0 { dst[off] = 48 as u8; return off + 1 } 98 let t: *u8 = sys_mmap(32) 99 var m: i64 = v 100 var k: i64 = 0 101 while m > 0 { t[k] = (48 + (m - (m / 10) * 10)) as u8; m = m / 10; k = k + 1 } 102 var o: i64 = off 103 while k > 0 { k = k - 1; dst[o] = t[k]; o = o + 1 } 104 return o 105} 106 107// REAL dimensions: PNG IHDR carries width/height as big-endian u32 at byte 16 and 20 (ECMA/PNG spec). 108// Returns 1 and fills out_wh[0]=w out_wh[1]=h, or 0 if this is not a PNG. 109func dx_png_dim(png: *u8, n: i64, out_wh: *i64) -> i64 { 110 if n < 24 { return 0 } 111 if png[0] != (137 as u8) { return 0 } 112 if png[1] != (80 as u8) { return 0 } 113 if png[2] != (78 as u8) { return 0 } 114 if png[3] != (71 as u8) { return 0 } 115 var w: i64 = 0 116 var i: i64 = 0 117 while i < 4 { w = w * 256 + (png[16 + i] as i64); i = i + 1 } 118 var h: i64 = 0 119 i = 0 120 while i < 4 { h = h * 256 + (png[20 + i] as i64); i = i + 1 } 121 out_wh[0] = w 122 out_wh[1] = h 123 return 1 124} 125 126// Inline image sized from the PNG's ACTUAL pixels -> EMU. rid must match a rel to word/media/imageN.png. 127func dx_emit_drawing(out: *u8, off: i64, rid: *u8, wpx: i64, hpx: i64, pid: i64, name: *u8) -> i64 { 128 var o: i64 = off 129 o = dx_cat(out, o, "<w:p><w:r><w:drawing><wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\"><wp:extent cx=\"" as *u8) 130 o = dx_catn(out, o, wpx * DX_EMU_PER_PX) 131 o = dx_cat(out, o, "\" cy=\"" as *u8) 132 o = dx_catn(out, o, hpx * DX_EMU_PER_PX) 133 o = dx_cat(out, o, "\"/><wp:docPr id=\"" as *u8) 134 o = dx_catn(out, o, pid) 135 o = dx_cat(out, o, "\" name=\"" as *u8) 136 o = dx_xml_escape(out, o, name) 137 o = dx_cat(out, o, "\"/><a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"><a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\"><pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\"><pic:nvPicPr><pic:cNvPr id=\"" as *u8) 138 o = dx_catn(out, o, pid) 139 o = dx_cat(out, o, "\" name=\"" as *u8) 140 o = dx_xml_escape(out, o, name) 141 o = dx_cat(out, o, "\"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed=\"" as *u8) 142 o = dx_cat(out, o, rid) 143 o = dx_cat(out, o, "\"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"" as *u8) 144 o = dx_catn(out, o, wpx * DX_EMU_PER_PX) 145 o = dx_cat(out, o, "\" cy=\"" as *u8) 146 o = dx_catn(out, o, hpx * DX_EMU_PER_PX) 147 o = dx_cat(out, o, "\"/></a:xfrm><a:prstGeom prst=\"rect\"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p>" as *u8) 148 return o 149} 150 151// Page layout: a real w:sectPr with pgSz + pgMar, referencing header1/footer1 parts. 152// Letter portrait = 12240 x 15840 twips; 1-inch margins = 1440 twips. 153func dx_emit_sectpr(out: *u8, off: i64, hdr_rid: *u8, ftr_rid: *u8) -> i64 { 154 var o: i64 = off 155 o = dx_cat(out, o, "<w:sectPr><w:headerReference w:type=\"default\" r:id=\"" as *u8) 156 o = dx_cat(out, o, hdr_rid) 157 o = dx_cat(out, o, "\"/><w:footerReference w:type=\"default\" r:id=\"" as *u8) 158 o = dx_cat(out, o, ftr_rid) 159 o = dx_cat(out, o, "\"/><w:pgSz w:w=\"12240\" w:h=\"15840\"/><w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" w:left=\"1440\" w:header=\"720\" w:footer=\"720\" w:gutter=\"0\"/></w:sectPr>" as *u8) 160 return o 161} 162 163// Tracked revisions: w:ins wraps inserted runs; w:del wraps deleted runs and the text becomes w:delText 164// (a plain w:t inside w:del is what makes Word silently drop the deletion on accept/reject). 165func dx_emit_track(out: *u8, off: i64, rev: i64, author: *u8, date: *u8, ins_txt: *u8, del_txt: *u8) -> i64 { 166 var o: i64 = off 167 o = dx_cat(out, o, "<w:p>" as *u8) 168 o = dx_cat(out, o, "<w:ins w:id=\"" as *u8) 169 o = dx_catn(out, o, rev) 170 o = dx_cat(out, o, "\" w:author=\"" as *u8) 171 o = dx_xml_escape(out, o, author) 172 o = dx_cat(out, o, "\" w:date=\"" as *u8) 173 o = dx_cat(out, o, date) 174 o = dx_cat(out, o, "\"><w:r><w:t xml:space=\"preserve\">" as *u8) 175 o = dx_xml_escape(out, o, ins_txt) 176 o = dx_cat(out, o, "</w:t></w:r></w:ins>" as *u8) 177 o = dx_cat(out, o, "<w:del w:id=\"" as *u8) 178 o = dx_catn(out, o, rev + 1) 179 o = dx_cat(out, o, "\" w:author=\"" as *u8) 180 o = dx_xml_escape(out, o, author) 181 o = dx_cat(out, o, "\" w:date=\"" as *u8) 182 o = dx_cat(out, o, date) 183 o = dx_cat(out, o, "\"><w:r><w:delText xml:space=\"preserve\">" as *u8) 184 o = dx_xml_escape(out, o, del_txt) 185 o = dx_cat(out, o, "</w:delText></w:r></w:del>" as *u8) 186 o = dx_cat(out, o, "</w:p>" as *u8) 187 return o 188} 189 190// Comment ANCHOR in the body: the range must be opened and closed around the commented run, and the 191// reference run carries the same id -- an orphan commentReference is what makes Word drop the comment. 192func dx_cm_add(out: *u8, off: i64, cid: i64, txt: *u8) -> i64 { 193 var o: i64 = off 194 o = dx_cat(out, o, "<w:p><w:commentRangeStart w:id=\"" as *u8) 195 o = dx_catn(out, o, cid) 196 o = dx_cat(out, o, "\"/><w:r><w:t xml:space=\"preserve\">" as *u8) 197 o = dx_xml_escape(out, o, txt) 198 o = dx_cat(out, o, "</w:t></w:r><w:commentRangeEnd w:id=\"" as *u8) 199 o = dx_catn(out, o, cid) 200 o = dx_cat(out, o, "\"/><w:r><w:rPr><w:rStyle w:val=\"CommentReference\"/></w:rPr><w:commentReference w:id=\"" as *u8) 201 o = dx_catn(out, o, cid) 202 o = dx_cat(out, o, "\"/></w:r></w:p>" as *u8) 203 return o 204} 205 206// word/comments.xml for ONE comment (the part the anchor above points at). 207func dx_cm_part(out: *u8, off: i64, cid: i64, author: *u8, initials: *u8, date: *u8, body: *u8) -> i64 { 208 var o: i64 = off 209 o = dx_cat(out, o, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:comments xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:comment w:id=\"" as *u8) 210 o = dx_catn(out, o, cid) 211 o = dx_cat(out, o, "\" w:author=\"" as *u8) 212 o = dx_xml_escape(out, o, author) 213 o = dx_cat(out, o, "\" w:initials=\"" as *u8) 214 o = dx_xml_escape(out, o, initials) 215 o = dx_cat(out, o, "\" w:date=\"" as *u8) 216 o = dx_cat(out, o, date) 217 o = dx_cat(out, o, "\"><w:p><w:r><w:t xml:space=\"preserve\">" as *u8) 218 o = dx_xml_escape(out, o, body) 219 o = dx_cat(out, o, "</w:t></w:r></w:p></w:comment></w:comments>" as *u8) 220 return o 221} 222 223// Footnote REFERENCE in the body. Authored notes start at id 2 because 0 and 1 are reserved for the 224// separator and continuationSeparator -- a note authored at id 0/1 collides with them and Word drops it. 225func dx_fn_add(out: *u8, off: i64, fid: i64, txt: *u8) -> i64 { 226 var o: i64 = off 227 o = dx_cat(out, o, "<w:p><w:r><w:t xml:space=\"preserve\">" as *u8) 228 o = dx_xml_escape(out, o, txt) 229 o = dx_cat(out, o, "</w:t></w:r><w:r><w:rPr><w:rStyle w:val=\"FootnoteReference\"/></w:rPr><w:footnoteReference w:id=\"" as *u8) 230 o = dx_catn(out, o, fid) 231 o = dx_cat(out, o, "\"/></w:r></w:p>" as *u8) 232 return o 233} 234 235// word/footnotes.xml with the two REQUIRED special notes pinned at ids 0 and 1, then the authored note. 236func dx_fn_part(out: *u8, off: i64, fid: i64, body: *u8) -> i64 { 237 var o: i64 = off 238 o = dx_cat(out, o, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:footnotes xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:footnote w:type=\"separator\" w:id=\"0\"><w:p><w:r><w:separator/></w:r></w:p></w:footnote><w:footnote w:type=\"continuationSeparator\" w:id=\"1\"><w:p><w:r><w:continuationSeparator/></w:r></w:p></w:footnote><w:footnote w:id=\"" as *u8) 239 o = dx_catn(out, o, fid) 240 o = dx_cat(out, o, "\"><w:p><w:r><w:rPr><w:rStyle w:val=\"FootnoteReference\"/></w:rPr><w:footnoteRef/></w:r><w:r><w:t xml:space=\"preserve\"> " as *u8) 241 o = dx_xml_escape(out, o, body) 242 o = dx_cat(out, o, "</w:t></w:r></w:p></w:footnote></w:footnotes>" as *u8) 243 return o 244} 245 246// ---- RICH .docx: formatted runs (bold/italic + heading size) + TABLES ---- 247// spec = one directive per line: "H <text>"=heading(bold,larger) "B <text>"=bold "I <text>"=italic 248// "P <text>"=plain "T c1|c2|c3"=a table row (consecutive T lines = ONE bordered table). Other lines = plain. 249// escape spec[s,e) (XML & < >) into dst; return new offset 250func dx_esc_span(dst: *u8, off: i64, spec: *u8, s: i64, e: i64) -> i64 { 251 var o: i64=off; var i: i64=s 252 while i<e { 253 let c: i64=spec[i] as i64 254 if c==38 { o=dx_cat(dst,o,"&amp;" as *u8) } else { if c==60 { o=dx_cat(dst,o,"&lt;" as *u8) } else { if c==62 { o=dx_cat(dst,o,"&gt;" as *u8) } else { dst[o]=spec[i]; o=o+1 } } } 255 i=i+1 256 } 257 return o 258} 259// emit one <w:p> paragraph; rpr (NUL-term, may be "") = the run-property block content (e.g. "<w:b/>") 260func dx_emit_para(out: *u8, o: i64, rpr: *u8, spec: *u8, s: i64, e: i64) -> i64 { 261 var oo: i64=o 262 oo=dx_cat(out,oo,"<w:p><w:r>" as *u8) 263 if rpr[0]!=(0 as u8) { oo=dx_cat(out,oo,"<w:rPr>" as *u8); oo=dx_cat(out,oo,rpr); oo=dx_cat(out,oo,"</w:rPr>" as *u8) } 264 oo=dx_cat(out,oo,"<w:t xml:space=\"preserve\">" as *u8) 265 oo=dx_esc_span(out,oo,spec,s,e) 266 oo=dx_cat(out,oo,"</w:t></w:r></w:p>" as *u8) 267 return oo 268} 269func dx_build_body_rich(spec: *u8, slen: i64, out: *u8) -> i64 { 270 var o: i64=0 271 // NOTE: never pass the "" string literal as the empty run-properties sentinel -- the nx_cc constant pool 272 // ALIASES the empty literal onto another literal (here "</w:tbl><w:p/>"), which would inject stray table 273 // closes into every plain paragraph. Use a real zeroed byte instead. 274 let EMPTY: *u8=sys_mmap(8); EMPTY[0]=0 as u8 275 o=dx_cat(out,o,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:body>" as *u8) 276 let TBLOPEN: *u8 = "<w:tbl><w:tblPr><w:tblW w:w=\"0\" w:type=\"auto\"/><w:tblBorders><w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/></w:tblBorders></w:tblPr>" as *u8 277 var intable: i64=0 278 var i: i64=0 279 while i<slen { 280 var e: i64=i 281 while e<slen { if spec[e]==(10 as u8) { break } e=e+1 } 282 var end: i64=e 283 if end>i { if spec[end-1]==(13 as u8) { end=end-1 } } 284 var c0: i64=0-1 285 if end>i { c0=spec[i] as i64 } 286 var typed: i64=0 287 var content: i64=i 288 if end>=i+2 { if spec[i+1]==(32 as u8) { 289 if c0==84 { typed=84 } 290 if c0==72 { typed=72 } 291 if c0==66 { typed=66 } 292 if c0==73 { typed=73 } 293 if c0==80 { typed=80 } 294 if typed!=0 { content=i+2 } 295 } } 296 if typed==84 { 297 if intable==0 { o=dx_cat(out,o,TBLOPEN); intable=1 } 298 o=dx_cat(out,o,"<w:tr>" as *u8) 299 var cs: i64=content 300 var p: i64=content 301 while p<=end { 302 var atbar: i64=0 303 if p==end { atbar=1 } else { if spec[p]==(124 as u8) { atbar=1 } } 304 if atbar==1 { 305 o=dx_cat(out,o,"<w:tc><w:tcPr><w:tcW w:w=\"0\" w:type=\"auto\"/></w:tcPr><w:p><w:r><w:t xml:space=\"preserve\">" as *u8) 306 o=dx_esc_span(out,o,spec,cs,p) 307 o=dx_cat(out,o,"</w:t></w:r></w:p></w:tc>" as *u8) 308 cs=p+1 309 } 310 p=p+1 311 } 312 o=dx_cat(out,o,"</w:tr>" as *u8) 313 } else { 314 if intable==1 { o=dx_cat(out,o,"</w:tbl>" as *u8); intable=0 } 315 if end>i { 316 if typed==72 { o=dx_emit_para(out,o,"<w:b/><w:sz w:val=\"32\"/><w:szCs w:val=\"32\"/>" as *u8, spec, content, end) } else { if typed==66 { o=dx_emit_para(out,o,"<w:b/>" as *u8, spec, content, end) } else { if typed==73 { o=dx_emit_para(out,o,"<w:i/>" as *u8, spec, content, end) } else { o=dx_emit_para(out,o,EMPTY, spec, content, end) } } } 317 } 318 } 319 i=e+1 320 } 321 if intable==1 { o=dx_cat(out,o,"</w:tbl><w:p/>" as *u8); intable=0 } 322 o=dx_cat(out,o,"</w:body></w:document>" as *u8) 323 return o 324} 325func docx_write_rich(path: *u8, spec: *u8, slen: i64) -> i64 { 326 let doc: *u8=sys_mmap(K_MAGIC_1048592) 327 let doclen: i64=dx_build_body_rich(spec, slen, doc) 328 return docx_write_document_xml(path, doc, doclen) 329} 330func dx_readfile(path: *u8, buf: *u8, cap: i64) -> i64 { 331 let fd: i64=sys_openat_rd(path); if fd<0 { return 0-1 } 332 var tot: i64=0 333 while tot<cap { let r: i64=sys_read(fd,(buf as i64+tot) as *u8,cap-tot); if r<=0 { break } tot=tot+r } 334 sys_close(fd); return tot 335} 336 337// extract a named OPC part's RAW bytes (delegates to the shared nx_opc reader) 338func docx_read_part(path: *u8, want: *u8, out: *u8, cap: i64) -> i64 { 339 return opc_read_part(path, want, out, cap) 340} 341 342// read word/document.xml text from a .docx into out (NUL-terminated); return length (or <0 on error) 343func docx_read_text(path: *u8, out: *u8, cap: i64) -> i64 { 344 let docbuf: *u8=sys_mmap(K_MAGIC_1048592) 345 let doclen: i64=docx_read_part(path, "word/document.xml" as *u8, docbuf, K_MAGIC_1048576) 346 if doclen<0 { return doclen } 347 // extract each <w:t ...>TEXT</w:t> run; join runs with newline; unescape entities 348 var o: i64=0; var i: i64=0; var first: i64=1 349 while i<doclen { 350 var advanced: i64=0 351 if docbuf[i]==(60 as u8) { // '<' 352 if dx_match(docbuf, i, doclen, "<w:t" as *u8)==1 { 353 let c4: i64=docbuf[i+4] as i64 354 if c4==62 { advanced=1 } else { if c4==32 { advanced=1 } } // only "<w:t>" or "<w:t " (not <w:tbl/<w:tc) 355 if advanced==1 { 356 var j: i64=i+4 357 while j<doclen { if docbuf[j]==(62 as u8) { break } j=j+1 } 358 j=j+1 // past '>' 359 if first==0 { out[o]=10 as u8; o=o+1 } 360 first=0 361 while j<doclen { 362 if dx_match(docbuf, j, doclen, "</w:t>" as *u8)==1 { break } 363 if docbuf[j]==(38 as u8) { // '&' entity 364 if dx_match(docbuf,j,doclen,"&amp;" as *u8)==1 { out[o]=38 as u8; o=o+1; j=j+5 } else { 365 if dx_match(docbuf,j,doclen,"&lt;" as *u8)==1 { out[o]=60 as u8; o=o+1; j=j+4 } else { 366 if dx_match(docbuf,j,doclen,"&gt;" as *u8)==1 { out[o]=62 as u8; o=o+1; j=j+4 } else { 367 if dx_match(docbuf,j,doclen,"&quot;" as *u8)==1 { out[o]=34 as u8; o=o+1; j=j+6 } else { 368 if dx_match(docbuf,j,doclen,"&apos;" as *u8)==1 { out[o]=39 as u8; o=o+1; j=j+6 } else { 369 out[o]=docbuf[j]; o=o+1; j=j+1 } } } } } 370 } else { out[o]=docbuf[j]; o=o+1; j=j+1 } 371 } 372 i=j+6 // past "</w:t>" 373 } 374 } 375 } 376 if advanced==0 { i=i+1 } 377 } 378 out[o]=0 as u8 379 return o 380} 381 382 383// ---- IMPORT (office LP1): document.xml -> the office SPEC grammar, PARAGRAPH-FAITHFUL ---------------------- 384// docx_read_text joins every <w:t> RUN with a newline, so a paragraph with a bold word becomes three lines; an 385// import built on it would shred every formatted paragraph. This reader keeps PARAGRAPH boundaries: runs inside 386// one <w:p> concatenate, </w:p> ends the line, a paragraph styled Heading*/Title (Word) or carrying the office's 387// own w:sz=32 heading convention becomes "H <text>", any other non-empty paragraph "P <text>", and a table row 388// becomes "T c1|c2|..." (cell paragraphs joined by a space; a literal '|' inside a cell becomes '/', the spec 389// delimiter cannot be escaped). Newline/tab inside a run become spaces because the spec is line-oriented. 390// Returns the spec length (NUL-terminated) or the part reader's negative code. 391const DX_SPEC_HDR_H: i64 = 72 // 'H' 392const DX_SPEC_HDR_P: i64 = 80 // 'P' 393const DX_SPEC_HDR_T: i64 = 84 // 'T' 394const DX_SPEC_SP: i64 = 32 395const DX_SPEC_NL: i64 = 10 396const DX_SPEC_PIPE: i64 = 124 397const DX_SPEC_SLASH: i64 = 47 398func dx_spec_put_text(dst: *u8, o0: i64, src: *u8, n: i64, in_tbl: i64) -> i64 { 399 var o: i64=o0; var k: i64=0 400 while k<n { 401 var c: i64=src[k] as i64 402 if c==DX_SPEC_NL { c=DX_SPEC_SP } 403 if c==9 { c=DX_SPEC_SP } 404 if in_tbl==1 { if c==DX_SPEC_PIPE { c=DX_SPEC_SLASH } } 405 dst[o]=c as u8; o=o+1; k=k+1 406 } 407 return o 408} 409func docx_read_spec(path: *u8, out: *u8, cap: i64) -> i64 { 410 let docbuf: *u8=sys_mmap(K_MAGIC_1048592) 411 let doclen: i64=docx_read_part(path, "word/document.xml" as *u8, docbuf, K_MAGIC_1048576) 412 if doclen<0 { return doclen } 413 let ptxt: *u8=sys_mmap(K_MAGIC_1048592) // current paragraph text 414 let row: *u8=sys_mmap(K_MAGIC_1048592) // current table row (cells joined by '|') 415 var o: i64=0; var i: i64=0 416 var pl: i64=0; var rl: i64=0 417 var in_p: i64=0; var heading: i64=0; var in_tbl: i64=0; var ncell: i64=0; var cell_has: i64=0 418 while i<doclen { 419 var adv: i64=0 420 if docbuf[i]==(60 as u8) { 421 if adv==0 { if dx_match(docbuf,i,doclen,"<w:tbl" as *u8)==1 { let c6: i64=docbuf[i+6] as i64; if c6==62 { in_tbl=1; adv=1 } else { if c6==32 { in_tbl=1; adv=1 } } } } 422 if adv==0 { if dx_match(docbuf,i,doclen,"</w:tbl>" as *u8)==1 { in_tbl=0; adv=1 } } 423 if adv==0 { if dx_match(docbuf,i,doclen,"<w:tr" as *u8)==1 { let c5: i64=docbuf[i+5] as i64; if c5==62 { rl=0; ncell=0; adv=1 } else { if c5==32 { rl=0; ncell=0; adv=1 } } } } 424 if adv==0 { if dx_match(docbuf,i,doclen,"</w:tr>" as *u8)==1 { 425 if rl>0 { if o+rl+3 < cap { out[o]=DX_SPEC_HDR_T as u8; out[o+1]=DX_SPEC_SP as u8; o=o+2; var q: i64=0; while q<rl { out[o]=row[q]; o=o+1; q=q+1 } out[o]=DX_SPEC_NL as u8; o=o+1 } } 426 rl=0; adv=1 } } 427 if adv==0 { if dx_match(docbuf,i,doclen,"<w:tc" as *u8)==1 { let c5b: i64=docbuf[i+5] as i64; var istc: i64=0; if c5b==62 { istc=1 } else { if c5b==32 { istc=1 } } 428 if istc==1 { if ncell>0 { row[rl]=DX_SPEC_PIPE as u8; rl=rl+1 } ncell=ncell+1; cell_has=0; adv=1 } } } 429 if adv==0 { if dx_match(docbuf,i,doclen,"<w:p" as *u8)==1 { let c4: i64=docbuf[i+4] as i64; if c4==62 { in_p=1; heading=0; pl=0; adv=1 } else { if c4==32 { in_p=1; heading=0; pl=0; adv=1 } } } } 430 if adv==0 { if dx_match(docbuf,i,doclen,"</w:p>" as *u8)==1 { 431 if pl>0 { 432 if in_tbl==1 { if cell_has==1 { row[rl]=DX_SPEC_SP as u8; rl=rl+1 } rl=dx_spec_put_text(row, rl, ptxt, pl, 1); cell_has=1 } 433 else { if o+pl+3 < cap { 434 if heading==1 { out[o]=DX_SPEC_HDR_H as u8 } else { out[o]=DX_SPEC_HDR_P as u8 } 435 out[o+1]=DX_SPEC_SP as u8; o=o+2 436 o=dx_spec_put_text(out, o, ptxt, pl, 0) 437 out[o]=DX_SPEC_NL as u8; o=o+1 } } 438 } 439 in_p=0; pl=0; adv=1 } } 440 if adv==0 { if in_p==1 { if dx_match(docbuf,i,doclen,"<w:pStyle w:val=\"Heading" as *u8)==1 { heading=1; adv=1 } } } 441 if adv==0 { if in_p==1 { if dx_match(docbuf,i,doclen,"<w:pStyle w:val=\"Title" as *u8)==1 { heading=1; adv=1 } } } 442 if adv==0 { if in_p==1 { if dx_match(docbuf,i,doclen,"<w:sz w:val=\"32\"" as *u8)==1 { heading=1; adv=1 } } } 443 if adv==0 { if dx_match(docbuf,i,doclen,"<w:t" as *u8)==1 { 444 let c4t: i64=docbuf[i+4] as i64 445 var ist: i64=0 446 if c4t==62 { ist=1 } else { if c4t==32 { ist=1 } } 447 if ist==1 { 448 var j: i64=i+4 449 while j<doclen { if docbuf[j]==(62 as u8) { break } j=j+1 } 450 j=j+1 451 while j<doclen { 452 if dx_match(docbuf, j, doclen, "</w:t>" as *u8)==1 { break } 453 if docbuf[j]==(38 as u8) { 454 if dx_match(docbuf,j,doclen,"&amp;" as *u8)==1 { ptxt[pl]=38 as u8; pl=pl+1; j=j+5 } else { 455 if dx_match(docbuf,j,doclen,"&lt;" as *u8)==1 { ptxt[pl]=60 as u8; pl=pl+1; j=j+4 } else { 456 if dx_match(docbuf,j,doclen,"&gt;" as *u8)==1 { ptxt[pl]=62 as u8; pl=pl+1; j=j+4 } else { 457 if dx_match(docbuf,j,doclen,"&quot;" as *u8)==1 { ptxt[pl]=34 as u8; pl=pl+1; j=j+6 } else { 458 if dx_match(docbuf,j,doclen,"&apos;" as *u8)==1 { ptxt[pl]=39 as u8; pl=pl+1; j=j+6 } else { 459 ptxt[pl]=docbuf[j]; pl=pl+1; j=j+1 } } } } } 460 } else { ptxt[pl]=docbuf[j]; pl=pl+1; j=j+1 } 461 } 462 i=j+6 463 adv=2 464 } 465 } } 466 } 467 if adv==0 { i=i+1 } 468 if adv==1 { i=i+1 } 469 } 470 out[o]=0 as u8 471 return o 472} 473// wrap the current run's inline text with <b>/<i> per the run flags (into the paragraph buffer) 474func dx_wrapopen(pb: *u8, o: i64, rb: i64, ri: i64) -> i64 { 475 var oo: i64=o 476 if rb==1 { oo=dx_cat(pb,oo,"<b>" as *u8) } 477 if ri==1 { oo=dx_cat(pb,oo,"<i>" as *u8) } 478 return oo 479} 480func dx_wrapclose(pb: *u8, o: i64, rb: i64, ri: i64) -> i64 { 481 var oo: i64=o 482 if ri==1 { oo=dx_cat(pb,oo,"</i>" as *u8) } 483 if rb==1 { oo=dx_cat(pb,oo,"</b>" as *u8) } 484 return oo 485} 486 487// Render a .docx (word/document.xml) into a clean, sovereign, 0-JS HTML PREVIEW page the Nishi browser shows 488// INLINE (browsers can't render raw .docx -- they download it; so we render OOXML -> HTML, the way Docs/Office 489// preview do). Handles paragraphs, headings (b + sz=32), bold/italic runs, and tables (tbl/tr/tc). XML text 490// entities (&amp; &lt; &gt;) are already valid HTML, so run text is copied verbatim. dlurl (optional) adds a 491// "Download .docx" interop link. Returns HTML length, or <0 if the part is missing. 492func dx_to_html(path: *u8, out: *u8, cap: i64, dlurl: *u8) -> i64 { 493 let doc: *u8=sys_mmap(K_MAGIC_1048592) 494 let dl: i64=docx_read_part(path, "word/document.xml" as *u8, doc, K_MAGIC_1048576) 495 if dl<0 { return dl } 496 var o: i64=0 497 o=dx_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}\nheader h1{margin:0;font-size:17px;font-weight:600}\n.doc{max-width:800px;margin:22px auto;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:34px 46px;box-shadow:0 1px 4px rgba(15,23,42,.08)}\n.doc h2{font-size:20px;color:#0f172a;margin:22px 0 8px;border-bottom:1px solid #eef2f7;padding-bottom:5px}\n.doc h2:first-child{margin-top:0}\n.doc p{margin:9px 0}\n.doc table{border-collapse:collapse;margin:16px 0;width:100%}\n.doc td{border:1px solid #cbd5e1;padding:7px 12px;font-size:15px;vertical-align:top}\n.bar{max-width:800px;margin:0 auto 6px;text-align:right}\n.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:800px;margin:14px auto 46px;color:#64748b;font-size:13px;text-align:center;line-height:1.5}\n</style></head>\n<body>\n<header><h1>&#128196; Document Preview &mdash; Nishi Office</h1></header>\n<div class=doc>\n" as *u8) 498 let pb: *u8=sys_mmap(K_MAGIC_262160) 499 var po: i64=0; var phead: i64=0; var incell: i64=0 500 var rb: i64=0; var ri: i64=0; var rhead: i64=0 501 var i: i64=0 502 while i<dl { 503 var m: i64=0 504 if m==0 { if dx_match(doc,i,dl,"<w:tbl>" as *u8)==1 { o=dx_cat(out,o,"<table>" as *u8); i=i+7; m=1 } } 505 if m==0 { if dx_match(doc,i,dl,"</w:tbl>" as *u8)==1 { o=dx_cat(out,o,"</table>" as *u8); i=i+8; m=1 } } 506 if m==0 { if dx_match(doc,i,dl,"<w:tr>" as *u8)==1 { o=dx_cat(out,o,"<tr>" as *u8); i=i+6; m=1 } } 507 if m==0 { if dx_match(doc,i,dl,"</w:tr>" as *u8)==1 { o=dx_cat(out,o,"</tr>" as *u8); i=i+7; m=1 } } 508 if m==0 { if dx_match(doc,i,dl,"<w:tc>" as *u8)==1 { o=dx_cat(out,o,"<td>" as *u8); incell=1; po=0; phead=0; i=i+6; m=1 } } 509 if m==0 { if dx_match(doc,i,dl,"</w:tc>" as *u8)==1 { pb[po]=0 as u8; o=dx_cat(out,o,pb); o=dx_cat(out,o,"</td>" as *u8); incell=0; po=0; i=i+7; m=1 } } 510 if m==0 { if dx_match(doc,i,dl,"<w:p>" as *u8)==1 { po=0; phead=0; i=i+5; m=1 } } 511 if m==0 { if dx_match(doc,i,dl,"</w:p>" as *u8)==1 { 512 if incell==0 { 513 pb[po]=0 as u8 514 if po>0 { if phead==1 { o=dx_cat(out,o,"<h2>" as *u8); o=dx_cat(out,o,pb); o=dx_cat(out,o,"</h2>" as *u8) } else { o=dx_cat(out,o,"<p>" as *u8); o=dx_cat(out,o,pb); o=dx_cat(out,o,"</p>" as *u8) } } 515 po=0 516 } 517 i=i+6; m=1 } } 518 if m==0 { if dx_match(doc,i,dl,"<w:r>" as *u8)==1 { rb=0; ri=0; rhead=0; i=i+5; m=1 } } 519 if m==0 { if dx_match(doc,i,dl,"<w:b/>" as *u8)==1 { rb=1; i=i+6; m=1 } } 520 if m==0 { if dx_match(doc,i,dl,"<w:i/>" as *u8)==1 { ri=1; i=i+6; m=1 } } 521 if m==0 { if dx_match(doc,i,dl,"<w:sz w:val=\"32\"" as *u8)==1 { rhead=1; phead=1; i=i+16; m=1 } } 522 if m==0 { if dx_match(doc,i,dl,"<w:t" as *u8)==1 { 523 let c4: i64=doc[i+4] as i64 524 var istext: i64=0 525 if c4==62 { istext=1 } else { if c4==32 { istext=1 } } 526 if istext==1 { 527 var j: i64=i+4 528 while j<dl { if doc[j]==(62 as u8) { break } j=j+1 } 529 j=j+1 530 if rhead==0 { po=dx_wrapopen(pb,po,rb,ri) } 531 while j<dl { 532 if dx_match(doc,j,dl,"</w:t>" as *u8)==1 { break } 533 pb[po]=doc[j]; po=po+1; j=j+1 534 } 535 if rhead==0 { po=dx_wrapclose(pb,po,rb,ri) } 536 i=j+6; m=1 537 } 538 } } 539 if m==0 { i=i+1 } 540 } 541 o=dx_cat(out,o,"</div>\n" as *u8) 542 if (dlurl as i64)!=0 { 543 o=dx_cat(out,o,"<div class=bar><a class=dl href=\"" as *u8); o=dx_cat(out,o,dlurl); o=dx_cat(out,o,"\">&#11015; Download .docx (opens in Word / Pages / Google Docs)</a></div>\n" as *u8) 544 } 545 o=dx_cat(out,o,"<footer>Rendered natively from OOXML by <b>nx_docx</b> &middot; viewable inline in the Nishi browser &middot; your documents stay on your hardware</footer>\n</body></html>\n" as *u8) 546 return o 547} 548 549 550// Package a .docx that USES every emitter above: header/footer parts + comments + footnotes + an inline 551// PNG, with the [Content_Types].xml overrides and word/_rels/document.xml.rels that make Word accept 552// them. Emitters that are never packaged are decoration -- this is what makes the axes real rather than 553// symbol-present, and `nx_docx feat <out.docx> <in.png>` exercises the whole path. 554func docx_write_featured(path: *u8, pngdata: *u8, pnglen: i64) -> i64 { 555 let doc: *u8 = sys_mmap(K_MAGIC_1048592) 556 var o: i64 = 0 557 o = dx_cat(doc, o, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\"><w:body>" as *u8) 558 o = dx_emit_track(doc, o, 100, "Nishi" as *u8, "2026-08-01T00:00:00Z" as *u8, "inserted text" as *u8, "deleted text" as *u8) 559 o = dx_cm_add(doc, o, 1, "commented sentence" as *u8) 560 o = dx_fn_add(doc, o, 2, "sentence with a footnote" as *u8) 561 let wh: *i64 = sys_mmap(16) as *i64 562 wh[0] = 0 563 wh[1] = 0 564 if pnglen > 0 { if dx_png_dim(pngdata, pnglen, wh) == 1 { o = dx_emit_drawing(doc, o, "rId5" as *u8, wh[0], wh[1], 1, "image1.png" as *u8) } } 565 o = dx_emit_sectpr(doc, o, "rId2" as *u8, "rId3" as *u8) 566 o = dx_cat(doc, o, "</w:body></w:document>" as *u8) 567 568 let cm: *u8 = sys_mmap(K_MAGIC_1048592) 569 let cmn: i64 = dx_cm_part(cm, 0, 1, "Nishi" as *u8, "NX" as *u8, "2026-08-01T00:00:00Z" as *u8, "a real anchored comment" as *u8) 570 let fn: *u8 = sys_mmap(K_MAGIC_1048592) 571 let fnn: i64 = dx_fn_part(fn, 0, 2, "a real footnote body" as *u8) 572 573 let hdr: *u8 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:hdr xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:p><w:r><w:t>Nishi header</w:t></w:r></w:p></w:hdr>" as *u8 574 let ftr: *u8 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:ftr xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:p><w:r><w:t>Nishi footer</w:t></w:r></w:p></w:ftr>" as *u8 575 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\"/><Default Extension=\"png\" ContentType=\"image/png\"/><Override PartName=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/><Override PartName=\"/word/header1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml\"/><Override PartName=\"/word/footer1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml\"/><Override PartName=\"/word/comments.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml\"/><Override PartName=\"/word/footnotes.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml\"/></Types>" as *u8 576 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=\"word/document.xml\"/></Relationships>" as *u8 577 let drels: *u8 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header\" Target=\"header1.xml\"/><Relationship Id=\"rId3\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer\" Target=\"footer1.xml\"/><Relationship Id=\"rId4\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments\" Target=\"comments.xml\"/><Relationship Id=\"rId6\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes\" Target=\"footnotes.xml\"/><Relationship Id=\"rId5\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\" Target=\"media/image1.png\"/></Relationships>" as *u8 578 579 let names: *i64 = sys_mmap(128) as *i64 580 let datap: *i64 = sys_mmap(128) as *i64 581 let lens: *i64 = sys_mmap(128) as *i64 582 names[0]="[Content_Types].xml" as *u8 as i64; datap[0]=ct as i64; lens[0]=dx_strlen(ct) 583 names[1]="_rels/.rels" as *u8 as i64; datap[1]=rels as i64; lens[1]=dx_strlen(rels) 584 names[2]="word/document.xml" as *u8 as i64; datap[2]=doc as i64; lens[2]=o 585 names[3]="word/_rels/document.xml.rels" as *u8 as i64; datap[3]=drels as i64; lens[3]=dx_strlen(drels) 586 names[4]="word/header1.xml" as *u8 as i64; datap[4]=hdr as i64; lens[4]=dx_strlen(hdr) 587 names[5]="word/footer1.xml" as *u8 as i64; datap[5]=ftr as i64; lens[5]=dx_strlen(ftr) 588 names[6]="word/comments.xml" as *u8 as i64; datap[6]=cm as i64; lens[6]=cmn 589 names[7]="word/footnotes.xml" as *u8 as i64; datap[7]=fn as i64; lens[7]=fnn 590 var nparts: i64 = 8 591 if pnglen > 0 { 592 names[8]="word/media/image1.png" as *u8 as i64; datap[8]=pngdata as i64; lens[8]=pnglen 593 nparts = 9 594 } 595 return opc_write(path, names, datap, lens, nparts) 596} 597 598func main(argc: i64, argv: *i64) -> i64 { 599 if argc >= 3 { 600 let v0: *u8 = argv[1] as *u8 601 if dx_streq(v0, "feat\x00" as *u8) == 1 { 602 let png: *u8 = sys_mmap(K_MAGIC_1048592) 603 var pn: i64 = 0 604 if argc >= 4 { pn = dx_readfile(argv[3] as *u8, png, K_MAGIC_1048592) } 605 if pn < 0 { pn = 0 } 606 let tot: i64 = docx_write_featured(argv[2] as *u8, png, pn) 607 dx_puts("DOCX-FEAT bytes=" as *u8); dx_num(tot) 608 dx_puts(" png_bytes=" as *u8); dx_num(pn); dx_puts("\n" as *u8) 609 return 0 610 } 611 } 612 if argc<3 { dx_puts("usage: nx_docx write <path.docx> <paragraph>... | writerich <path.docx> <specfile> | read <path.docx> | html <path.docx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 } 613 let cmd: *u8=argv[1] as *u8 614 let path: *u8=argv[2] as *u8 615 if dx_streq(cmd, "write" as *u8)==1 { 616 let paras: *i64=sys_mmap(8*256) as *i64 617 var np: i64=0; var i: i64=3 618 while i<argc { paras[np]=argv[i]; np=np+1; i=i+1 } 619 if np==0 { paras[0]="(empty document)" as *u8 as i64; np=1 } 620 let sz: i64=docx_write(path, paras, np) 621 if sz<0 { dx_puts("DOCX-WRITE-FAIL\n" as *u8); sys_exit(1); return 1 } 622 dx_puts("DOCX-WRITE-OK path=" as *u8); dx_puts(path); dx_puts(" bytes=" as *u8); dx_num(sz); dx_puts(" paragraphs=" as *u8); dx_num(np); dx_puts("\n" as *u8) 623 sys_exit(0); return 0 624 } 625 if dx_streq(cmd, "writerich" as *u8)==1 { 626 if argc<4 { dx_puts("usage: nx_docx writerich <path.docx> <specfile>\n" as *u8); sys_exit(2); return 2 } 627 let spec: *u8=sys_mmap(K_MAGIC_1048592) 628 let slen: i64=dx_readfile(argv[3] as *u8, spec, K_MAGIC_1048576) 629 if slen<=0 { dx_puts("DOCX-RICH-FAIL specfile-missing-or-empty\n" as *u8); sys_exit(1); return 1 } 630 let sz: i64=docx_write_rich(path, spec, slen) 631 if sz<0 { dx_puts("DOCX-RICH-FAIL write\n" as *u8); sys_exit(1); return 1 } 632 dx_puts("DOCX-WRITE-RICH-OK path=" as *u8); dx_puts(path); dx_puts(" bytes=" as *u8); dx_num(sz); dx_puts("\n" as *u8) 633 sys_exit(0); return 0 634 } 635 if dx_streq(cmd, "read" as *u8)==1 { 636 let out: *u8=sys_mmap(K_MAGIC_1048592) 637 let tl: i64=docx_read_text(path, out, K_MAGIC_1048576) 638 if tl<0 { dx_puts("DOCX-READ-FAIL code=" as *u8); dx_num(tl); dx_puts("\n" as *u8); sys_exit(1); return 1 } 639 dx_puts("DOCX-TEXT-BEGIN\n" as *u8) 640 sys_write(1, out, tl) 641 dx_puts("\nDOCX-TEXT-END len=" as *u8); dx_num(tl); dx_puts("\n" as *u8) 642 sys_exit(0); return 0 643 } 644 if dx_streq(cmd, "spec" as *u8)==1 { 645 // office import: document.xml -> the spec grammar, one H/P/T line per paragraph or table row 646 let out: *u8=sys_mmap(K_MAGIC_1048592) 647 let sl: i64=docx_read_spec(path, out, K_MAGIC_1048576) 648 if sl<0 { dx_puts("DOCX-SPEC-FAIL code=" as *u8); dx_num(sl); dx_puts("\n" as *u8); sys_exit(1); return 1 } 649 dx_puts("DOCX-SPEC-BEGIN\n" as *u8) 650 sys_write(1, out, sl) 651 dx_puts("DOCX-SPEC-END len=" as *u8); dx_num(sl); dx_puts("\n" as *u8) 652 sys_exit(0); return 0 653 } 654 if dx_streq(cmd, "html" as *u8)==1 { 655 if argc<4 { dx_puts("usage: nx_docx html <path.docx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 } 656 let outp: *u8=argv[3] as *u8 657 var dlu: *u8=0 as *u8 658 if argc>4 { dlu=argv[4] as *u8 } 659 let out: *u8=sys_mmap(K_MAGIC_2097168) 660 let n: i64=dx_to_html(path, out, K_MAGIC_2097152, dlu) 661 if n<0 { dx_puts("DOCX-HTML-FAIL code=" as *u8); dx_num(n); dx_puts("\n" as *u8); sys_exit(1); return 1 } 662 let fd: i64=sys_openat_wr(outp, 0x1a4) 663 if fd<0 { dx_puts("DOCX-HTML-FAIL cannot-write\n" as *u8); sys_exit(1); return 1 } 664 sys_write(fd, out, n); sys_close(fd) 665 dx_puts("DOCX-HTML-OK path=" as *u8); dx_puts(outp); dx_puts(" bytes=" as *u8); dx_num(n); dx_puts("\n" as *u8) 666 sys_exit(0); return 0 667 } 668 dx_puts("nx_docx: unknown command (write|writerich|read|html)\n" as *u8) 669 sys_exit(2); return 2 670}