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,"&" as *u8) } else { if c==60 { o=ot_cat(dst,o,"<" as *u8) } else { if c==62 { o=ot_cat(dst,o,">" 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,"&" as *u8)==1 { out[o]=38 as u8; o=o+1; j=j+5 } else {
71 if ot_match(cbuf,j,n,"<" as *u8)==1 { out[o]=60 as u8; o=o+1; j=j+4 } else {
72 if ot_match(cbuf,j,n,">" as *u8)==1 { out[o]=62 as u8; o=o+1; j=j+4 } else {
73 if ot_match(cbuf,j,n,""" as *u8)==1 { out[o]=34 as u8; o=o+1; j=j+6 } else {
74 if ot_match(cbuf,j,n,"'" 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}