code wiki / _hdl_build / nx_odt.nx

nx_odt.nx source

↩ module page · 108 lines · 7178 B

1// nx_odt.nx -- SOVEREIGN OpenDocument Text (.odt / ODF) interop for the Nishi Office suite. .odt is a ZIP (framed 2// by the shared nx_opc) of XML parts -- the format LibreOffice / OpenOffice / Google Docs use natively. So with 3// nx_docx (.docx) + nx_odt (.odt) Nishi interops with BOTH dominant office families. 4// write : paragraphs -> mimetype (STORED, FIRST entry per ODF) + META-INF/manifest.xml + content.xml 5// read : a .odt -> the text of content.xml (extracts <text:p> paragraphs, strips inner spans, unescapes XML) 6// ODF requires the `mimetype` part to be the FIRST entry and STORED (uncompressed) so the package magic is 7// detectable -- opc_write writes STORED with no extra field, and we pass mimetype first, so that holds. 8// nx_odt write <path.odt> <paragraph1> [paragraph2 ...] | nx_odt read <path.odt> 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_opc.nx" 12const K_MAGIC_1048592: i64 = 1048592 13const K_MAGIC_1048576: i64 = 1048576 14 15func ot_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 16func ot_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; 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 i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } 17func ot_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 18func ot_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 } 19func ot_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 } 20func ot_match(buf: *u8, pos: i64, n: i64, pat: *u8) -> i64 { 21 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 22} 23func ot_xml_escape(dst: *u8, off: i64, s: *u8) -> i64 { 24 var o: i64=off; var i: i64=0 25 while s[i]!=(0 as u8) { 26 let c: i64=s[i] as i64 27 if c==38 { o=ot_cat(dst,o,"&amp;" as *u8) } else { if c==60 { o=ot_cat(dst,o,"&lt;" as *u8) } else { if c==62 { o=ot_cat(dst,o,"&gt;" as *u8) } else { dst[o]=s[i]; o=o+1 } } } 28 i=i+1 29 } 30 return o 31} 32func ot_build_content(paras: *i64, nparas: i64, out: *u8) -> i64 { 33 var o: i64=ot_cat(out, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" office:version=\"1.2\"><office:body><office:text>" as *u8) 34 var i: i64=0 35 while i<nparas { o=ot_cat(out,o,"<text:p>" as *u8); o=ot_xml_escape(out,o,paras[i] as *u8); o=ot_cat(out,o,"</text:p>" as *u8); i=i+1 } 36 o=ot_cat(out,o,"</office:text></office:body></office:document-content>" as *u8) 37 return o 38} 39 40func odt_write(path: *u8, paras: *i64, nparas: i64) -> i64 { 41 let content: *u8=sys_mmap(K_MAGIC_1048592) 42 let clen: i64=ot_build_content(paras, nparas, content) 43 let mime: *u8="application/vnd.oasis.opendocument.text" as *u8 44 let manifest: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\" manifest:version=\"1.2\"><manifest:file-entry manifest:full-path=\"/\" manifest:media-type=\"application/vnd.oasis.opendocument.text\"/><manifest:file-entry manifest:full-path=\"content.xml\" manifest:media-type=\"text/xml\"/></manifest:manifest>" as *u8 45 let names: *i64=sys_mmap(64) as *i64; let datap: *i64=sys_mmap(64) as *i64; let lens: *i64=sys_mmap(64) as *i64 46 names[0]="mimetype" as *u8 as i64; datap[0]=mime as i64; lens[0]=ot_strlen(mime) 47 names[1]="META-INF/manifest.xml" as *u8 as i64; datap[1]=manifest as i64; lens[1]=ot_strlen(manifest) 48 names[2]="content.xml" as *u8 as i64; datap[2]=content as i64; lens[2]=clen 49 return opc_write(path, names, datap, lens, 3) 50} 51 52func odt_read_text(path: *u8, out: *u8, cap: i64) -> i64 { 53 let cbuf: *u8=sys_mmap(K_MAGIC_1048592) 54 let n: i64=opc_read_part(path, "content.xml" as *u8, cbuf, K_MAGIC_1048576) 55 if n<0 { return n } 56 var o: i64=0; var i: i64=0; var first: i64=1 57 while i<n { 58 var advanced: i64=0 59 if ot_match(cbuf,i,n,"<text:p" as *u8)==1 { 60 let c7: i64=cbuf[i+7] as i64 61 if c7==62 { advanced=1 } else { if c7==32 { advanced=1 } } // "<text:p>" or "<text:p " 62 if advanced==1 { 63 var j: i64=i+7; while j<n { if cbuf[j]==(62 as u8) { break } j=j+1 } j=j+1 // past '>' 64 if first==0 { out[o]=10 as u8; o=o+1 } 65 first=0 66 while j<n { 67 if ot_match(cbuf,j,n,"</text:p>" as *u8)==1 { break } 68 if cbuf[j]==(60 as u8) { while j<n { if cbuf[j]==(62 as u8) { j=j+1; break } j=j+1 } } // strip inner tag (e.g. <text:span>) 69 else { if cbuf[j]==(38 as u8) { 70 if ot_match(cbuf,j,n,"&amp;" as *u8)==1 { out[o]=38 as u8; o=o+1; j=j+5 } else { 71 if ot_match(cbuf,j,n,"&lt;" as *u8)==1 { out[o]=60 as u8; o=o+1; j=j+4 } else { 72 if ot_match(cbuf,j,n,"&gt;" as *u8)==1 { out[o]=62 as u8; o=o+1; j=j+4 } else { 73 if ot_match(cbuf,j,n,"&quot;" as *u8)==1 { out[o]=34 as u8; o=o+1; j=j+6 } else { 74 if ot_match(cbuf,j,n,"&apos;" as *u8)==1 { out[o]=39 as u8; o=o+1; j=j+6 } else { 75 out[o]=cbuf[j]; o=o+1; j=j+1 } } } } } 76 } else { out[o]=cbuf[j]; o=o+1; j=j+1 } } 77 } 78 i=j+9 // past "</text:p>" 79 } 80 } 81 if advanced==0 { i=i+1 } 82 } 83 out[o]=0 as u8 84 return o 85} 86 87func main(argc: i64, argv: *i64) -> i64 { 88 if argc<3 { ot_puts("usage: nx_odt write <path.odt> <paragraph>... | read <path.odt>\n" as *u8); sys_exit(2); return 2 } 89 let cmd: *u8=argv[1] as *u8; let path: *u8=argv[2] as *u8 90 if ot_streq(cmd,"write" as *u8)==1 { 91 let paras: *i64=sys_mmap(8*256) as *i64; var np: i64=0; var i: i64=3 92 while i<argc { paras[np]=argv[i]; np=np+1; i=i+1 } 93 if np==0 { paras[0]="(empty document)" as *u8 as i64; np=1 } 94 let sz: i64=odt_write(path, paras, np) 95 if sz<0 { ot_puts("ODT-WRITE-FAIL\n" as *u8); sys_exit(1); return 1 } 96 ot_puts("ODT-WRITE-OK path=" as *u8); ot_puts(path); ot_puts(" bytes=" as *u8); ot_num(sz); ot_puts(" paragraphs=" as *u8); ot_num(np); ot_puts("\n" as *u8) 97 sys_exit(0); return 0 98 } 99 if ot_streq(cmd,"read" as *u8)==1 { 100 let out: *u8=sys_mmap(K_MAGIC_1048592) 101 let tl: i64=odt_read_text(path, out, K_MAGIC_1048576) 102 if tl<0 { ot_puts("ODT-READ-FAIL code=" as *u8); ot_num(tl); ot_puts("\n" as *u8); sys_exit(1); return 1 } 103 ot_puts("ODT-TEXT-BEGIN\n" as *u8); sys_write(1, out, tl); ot_puts("\nODT-TEXT-END len=" as *u8); ot_num(tl); ot_puts("\n" as *u8) 104 sys_exit(0); return 0 105 } 106 ot_puts("nx_odt: unknown command (write|read)\n" as *u8) 107 sys_exit(2); return 2 108}