From 4ae4f0fad91cb96389e59e81b801062b4f3ab992 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Mon, 26 Sep 2022 13:51:47 -0400 Subject: [PATCH] NUMBERS write multiple worksheets [ci skip] --- modules/83_numbers.ts | 484 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 444 insertions(+), 40 deletions(-) diff --git a/modules/83_numbers.ts b/modules/83_numbers.ts index 2a7585a..2c2faee 100644 --- a/modules/83_numbers.ts +++ b/modules/83_numbers.ts @@ -148,6 +148,22 @@ function varint_to_i32(buf: Uint8Array): number { } return i32; } +/** Parse a 64-bit unsigned integer as a pair */ +function varint_to_u64(buf: Uint8Array): [number, number] { + var l = 0, lo = buf[l] & 0x7F, hi = 0; + varint: if(buf[l++] >= 0x80) { + lo |= (buf[l] & 0x7F) << 7; if(buf[l++] < 0x80) break varint; + lo |= (buf[l] & 0x7F) << 14; if(buf[l++] < 0x80) break varint; + lo |= (buf[l] & 0x7F) << 21; if(buf[l++] < 0x80) break varint; + lo |= (buf[l] & 0x7F) << 28; hi = (buf[l] >> 4) & 0x07; if(buf[l++] < 0x80) break varint; + hi |= (buf[l] & 0x7F) << 3; if(buf[l++] < 0x80) break varint; + hi |= (buf[l] & 0x7F) << 10; if(buf[l++] < 0x80) break varint; + hi |= (buf[l] & 0x7F) << 17; if(buf[l++] < 0x80) break varint; + hi |= (buf[l] & 0x7F) << 24; if(buf[l++] < 0x80) break varint; + hi |= (buf[l] & 0x7F) << 31; + } + return [lo >>> 0, hi >>> 0]; +} //< r != ref)) }]; +} + type MessageSpace = {[id: number]: IWAMessage[]}; /** Parse .TST.TableDataList */ @@ -621,6 +652,7 @@ function parse_TST_TableDataList(M: MessageSpace, root: IWAMessage): any[] { (entries||[]).forEach(entry => { // .TST.TableDataList.ListEntry var le = parse_shallow(entry.data); + if(!le[1]) return; // sometimes the list has a spurious entry at index 0 var key = varint_to_i32(le[1][0].data)>>>0; switch(type) { case 1: data[key] = u8str(le[3][0].data); break; @@ -853,7 +885,7 @@ function parse_numbers_iwa(cfb: CFB$Container, opts?: ParsingOptions ): WorkBook if(!indices.length) throw new Error("File has no messages"); /* find document root */ - if(M?.[1]?.[0]?.meta?.[1]?.[0].data && varint_to_i32(M[1][0].meta[1][0].data) == 10000) throw new Error("Pages documents are not supported"); + if(M?.[1]?.[0].meta?.[1]?.[0].data && varint_to_i32(M[1][0].meta[1][0].data) == 10000) throw new Error("Pages documents are not supported"); var docroot: IWAMessage | false = M?.[1]?.[0]?.meta?.[1]?.[0].data && varint_to_i32(M[1][0].meta[1][0].data) == 1 && M[1][0]; if(!docroot) indices.forEach((idx) => { M[idx].forEach((iwam) => { @@ -876,7 +908,18 @@ interface DependentInfo { type: number; } /** Write .TST.TileRowInfo */ -function write_tile_row(tri: ProtoMessage, data: any[], SST: string[], wide: boolean): number { +function write_TST_TileRowInfo(data: any[], SST: string[], wide: boolean): ProtoMessage { + var tri: ProtoMessage = [ + [], + [ { type: 0, data: write_varint49(0) }], + [ { type: 0, data: write_varint49(0) }], + [ { type: 2, data: new Uint8Array([]) }], + [ { type: 2, data: new Uint8Array(Array.from({length:510}, () => 255)) }], + [ { type: 0, data: write_varint49(5) }], + [ { type: 2, data: new Uint8Array([]) }], + [ { type: 2, data: new Uint8Array(Array.from({length:510}, () => 255)) }], + [ { type: 0, data: write_varint49(1) }], + ] as ProtoMessage; if(!tri[6]?.[0] || !tri[7]?.[0]) throw "Mutation only works on post-BNC storages!"; //var wide_offsets = tri[8]?.[0]?.data && varint_to_i32(tri[8][0].data) > 0 || false; var cnt = 0; @@ -930,7 +973,7 @@ function write_tile_row(tri: ProtoMessage, data: any[], SST: string[], wide: boo tri[6][0].data = u8concat(cell_storage); /*if(!wide)*/ tri[3][0].data = u8concat(_cell_storage); tri[8] = [{type: 0, data: write_varint49(wide ? 1 : 0)}]; - return cnt; + return tri; } /** Write IWA Message */ @@ -1000,16 +1043,21 @@ function write_numbers_iwa(wb: WorkBook, opts?: WritingOptions): CFB$Container { /* read template and build packet metadata */ var cfb: CFB$Container = CFB.read(opts.numbers, { type: "base64" }); - var dependents: Dependents = build_numbers_deps(cfb); + var deps: Dependents = build_numbers_deps(cfb); /* .TN.DocumentArchive */ - var cfb_DA = CFB.find(cfb, dependents[1].location); - if(!cfb_DA) throw `Could not find ${dependents[1].location} in Numbers template`; - var iwa_DA = parse_iwa_file(decompress_iwa_file(cfb_DA.content as Uint8Array)); - var docroot: IWAArchiveInfo = iwa_DA.find(packet => packet.id == 1) as IWAArchiveInfo; + var docroot: IWAArchiveInfo = numbers_iwa_find(cfb, deps, 1); if(docroot == null) throw `Could not find message ${1} in Numbers template`; var sheetrefs = mappa(parse_shallow(docroot.messages[0].data)[1], parse_TSP_Reference); - wb.SheetNames.forEach((name, idx) => write_numbers_ws(cfb, dependents, wb.Sheets[name], name, idx, sheetrefs[idx])); + if(sheetrefs.length > 1) throw new Error("Template NUMBERS file must have exactly one sheet") + wb.SheetNames.forEach((name, idx) => { + if(idx >= 1) { + numbers_add_ws(cfb, deps, idx + 1); + docroot = numbers_iwa_find(cfb, deps, 1); + sheetrefs = mappa(parse_shallow(docroot.messages[0].data)[1], parse_TSP_Reference); + } + write_numbers_ws(cfb, deps, wb.Sheets[name], name, idx, sheetrefs[idx]) + }); return cfb; } @@ -1034,10 +1082,377 @@ function numbers_iwa_find(cfb: CFB$Container, deps: Dependents, id: number) { return ainfo; } +/** Deep copy of the essential parts of a worksheet */ +function numbers_add_ws(cfb: CFB$Container, deps: Dependents, wsidx: number) { + var sheetref = -1, newsheetref = -1; + + var remap: {[x: number]: number} = {}; + /* .TN.DocumentArchive -> new .TN.SheetArchive */ + numbers_iwa_doit(cfb, deps, 1, (docroot: IWAArchiveInfo, arch: IWAArchiveInfo[]) => { + var doc = parse_shallow(docroot.messages[0].data); + + /* new sheet reference */ + sheetref = parse_TSP_Reference(parse_shallow(docroot.messages[0].data)[1][0].data); + newsheetref = get_unique_msgid({ deps: [1], location: deps[sheetref].location, type: 2 }, deps); + remap[sheetref] = newsheetref; + + /* connect root -> sheet */ + numbers_add_oref(docroot, newsheetref); + doc[1].push({ type: 2, data: write_TSP_Reference(newsheetref) }); + + /* copy sheet */ + var sheet = numbers_iwa_find(cfb, deps, sheetref); + sheet.id = newsheetref; + if(deps[1].location == deps[newsheetref].location) arch.push(sheet); + else numbers_iwa_doit(cfb, deps, newsheetref, (_, x) => x.push(sheet)); + + docroot.messages[0].data = write_shallow(doc); + }); + + /* .TN.SheetArchive -> new .TST.TableInfoArchive */ + var tiaref = -1; + numbers_iwa_doit(cfb, deps, newsheetref, (sheetroot: IWAArchiveInfo, arch: IWAArchiveInfo[]) => { + var sa = parse_shallow(sheetroot.messages[0].data); + + /* remove everything except for name and drawables */ + for(var i = 3; i <= 69; ++i) delete sa[i]; + /* remove all references to drawables */ + var drawables = mappa(sa[2], parse_TSP_Reference); + drawables.forEach(n => numbers_del_oref(sheetroot, n)); + + /* add new tia reference */ + tiaref = get_unique_msgid({ deps: [newsheetref], location: deps[drawables[0]].location, type: deps[drawables[0]].type }, deps); + numbers_add_oref(sheetroot, tiaref); + remap[drawables[0]] = tiaref; + sa[2] = [ { type: 2, data: write_TSP_Reference(tiaref) } ]; + + /* copy tia */ + var tia = numbers_iwa_find(cfb, deps, drawables[0]); + tia.id = tiaref; + if(deps[drawables[0]].location == deps[newsheetref].location) arch.push(tia); + else { + var loc = deps[newsheetref].location; + loc = loc.replace(/^Root Entry\//,""); // NOTE: the Root Entry prefix is an artifact of the CFB container library + loc = loc.replace(/^Index\//, "").replace(/\.iwa$/,""); + numbers_iwa_doit(cfb, deps, 2, (ai => { + var mlist = parse_shallow(ai.messages[0].data); + + /* add reference from SheetArchive file to TIA */ + var parentidx = mlist[3].findIndex(m => { + var mm = parse_shallow(m.data); + if(mm[3]?.[0]) return u8str(mm[3][0].data) == loc; + if(mm[2]?.[0] && u8str(mm[2][0].data) == loc) return true; + return false; + }); + var parent = parse_shallow(mlist[3][parentidx].data); + if(!parent[6]) parent[6] = []; + parent[6].push({ + type: 2, + data: write_shallow([ + [], + [{type: 0, data: write_varint49(tiaref) }] + ]) + }); + mlist[3][parentidx].data = write_shallow(parent); + + ai.messages[0].data = write_shallow(mlist); + })); + numbers_iwa_doit(cfb, deps, tiaref, (_, x) => x.push(tia)); + } + + sheetroot.messages[0].data = write_shallow(sa); + }); + + /* .TST.TableInfoArchive -> new .TST.TableModelArchive */ + var tmaref = -1; + numbers_iwa_doit(cfb, deps, tiaref, (tiaroot: IWAArchiveInfo, arch: IWAArchiveInfo[]) => { + var tia = parse_shallow(tiaroot.messages[0].data); + + /* update parent reference */ + var da = parse_shallow(tia[1][0].data); + for(var i = 3; i <= 69; ++i) delete da[i]; + var dap = parse_TSP_Reference(da[2][0].data); + da[2][0].data = write_TSP_Reference(remap[dap]); + tia[1][0].data = write_shallow(da); + + /* remove old tma reference */ + var oldtmaref = parse_TSP_Reference(tia[2][0].data); + numbers_del_oref(tiaroot, oldtmaref); + + /* add new tma reference */ + tmaref = get_unique_msgid({ deps: [tiaref], location: deps[oldtmaref].location, type: deps[oldtmaref].type }, deps); + numbers_add_oref(tiaroot, tmaref); + remap[oldtmaref] = tmaref; + tia[2][0].data = write_TSP_Reference(tmaref); + + /* copy tma */ + var tma = numbers_iwa_find(cfb, deps, oldtmaref); + tma.id = tmaref; + if(deps[tiaref].location == deps[tmaref].location) arch.push(tma); + else numbers_iwa_doit(cfb, deps, tmaref, (_, x) => x.push(tma)); + + tiaroot.messages[0].data = write_shallow(tia); + }); + + /* identifier for finding the TableModelArchive in the archive */ + var loc = deps[tmaref].location; + loc = loc.replace(/^Root Entry\//,""); // NOTE: the Root Entry prefix is an artifact of the CFB container library + loc = loc.replace(/^Index\//, "").replace(/\.iwa$/,""); + + /* .TST.TableModelArchive */ + numbers_iwa_doit(cfb, deps, tmaref, (tmaroot: IWAArchiveInfo, arch: IWAArchiveInfo[]) => { + /* TODO: formulae currently break due to missing CE details */ + var tma = parse_shallow(tmaroot.messages[0].data); + var uuid = u8str(tma[1][0].data), new_uuid = uuid.replace(/-[A-Z0-9]*/, `-${wsidx.toString(16).padStart(4, "0")}`); + tma[1][0].data = stru8(new_uuid); + + /* NOTE: These lists should be revisited every time the template is changed */ + + /* bare fields */ + [ 12, 13, 29, 31, 32, 33, 39, 44, 47, 81, 82, 84 ].forEach(n => delete tma[n]); + + if(tma[45]) { + // .TST.SortRuleReferenceTrackerArchive + var srrta = parse_shallow(tma[45][0].data); + var ref = parse_TSP_Reference(srrta[1][0].data); + numbers_del_oref(tmaroot, ref); + delete tma[45]; + } + + if(tma[70]) { + // .TST.HiddenStatesOwnerArchive + var hsoa = parse_shallow(tma[70][0].data); + hsoa[2]?.forEach(item => { + // .TST.HiddenStatesArchive + var hsa = parse_shallow(item.data); + [2,3].map(n => hsa[n][0]).forEach(hseadata => { + // .TST.HiddenStateExtentArchive + var hsea = parse_shallow(hseadata.data); + if(!hsea[8]) return; + var ref = parse_TSP_Reference(hsea[8][0].data); + numbers_del_oref(tmaroot, ref); + }); + }); + delete tma[70]; + } + + [ 46, // deleting field 46 (base_column_row_uids) forces Numbers to refresh cell table + 30, 34, 35, 36, 38, 48, 49, 60, 61, 62, 63, 64, 71, 72, 73, 74, 75, 85, 86, 87, 88, 89 + ].forEach(n => { + if(!tma[n]) return; + var ref = parse_TSP_Reference(tma[n][0].data); + delete tma[n]; + numbers_del_oref(tmaroot, ref); + }); + + /* update .TST.DataStore */ + var store = parse_shallow(tma[4][0].data); + { + /* TODO: actually scan through dep tree and update */ + + /* blind copy of single references */ + [ 2, 4, 5, 6, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22 ].forEach(n => { + if(!store[n]?.[0]) return; + var oldref = parse_TSP_Reference(store[n][0].data); + var newref = get_unique_msgid({ deps: [tmaref], location: deps[oldref].location, type: deps[oldref].type }, deps); + numbers_del_oref(tmaroot, oldref); + numbers_add_oref(tmaroot, newref); + remap[oldref] = newref; + var msg = numbers_iwa_find(cfb, deps, oldref); + msg.id = newref; + if(deps[oldref].location == deps[tmaref].location) arch.push(msg); + else { + deps[newref].location = deps[oldref].location.replace(oldref.toString(), newref.toString()); + if(deps[newref].location == deps[oldref].location) deps[newref].location = deps[newref].location.replace(/\.iwa/, `-${newref}.iwa`); + CFB.utils.cfb_add(cfb, deps[newref].location, compress_iwa_file(write_iwa_file([ msg ]))); + + var newloc = deps[newref].location; + newloc = newloc.replace(/^Root Entry\//,""); // NOTE: the Root Entry prefix is an artifact of the CFB container library + newloc = newloc.replace(/^Index\//, "").replace(/\.iwa$/,""); + + numbers_iwa_doit(cfb, deps, 2, (ai => { + var mlist = parse_shallow(ai.messages[0].data); + mlist[3].push({type: 2, data: write_shallow([ + [], + [{type: 0, data: write_varint49(newref)}], + [{type: 2, data: stru8(newloc.replace(/-.*$/, "")) }], + [{type: 2, data: stru8(newloc)}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [], + [], + [], + [], + [{type: 0, data: write_varint49(0)}], + [], + [{type: 0, data: write_varint49(0 /* TODO: save_token */)}], + ])}); + mlist[1] = [{type: 0, data: write_varint49(Math.max(newref + 1, parse_varint49(mlist[1][0].data) ))}]; + + /* add reference from TableModelArchive file to Tile */ + var parentidx = mlist[3].findIndex(m => { + var mm = parse_shallow(m.data); + if(mm[3]?.[0]) return u8str(mm[3][0].data) == loc; + if(mm[2]?.[0] && u8str(mm[2][0].data) == loc) return true; + return false; + }); + var parent = parse_shallow(mlist[3][parentidx].data); + if(!parent[6]) parent[6] = []; + parent[6].push({ + type: 2, + data: write_shallow([ + [], + [{type: 0, data: write_varint49(newref) }] + ]) + }); + mlist[3][parentidx].data = write_shallow(parent); + + ai.messages[0].data = write_shallow(mlist); + })); + } + store[n][0].data = write_TSP_Reference(newref); + }); + + /* copy row header storage */ + var row_headers = parse_shallow(store[1][0].data); + { + row_headers[2]?.forEach(tspref => { + var oldref = parse_TSP_Reference(tspref.data); + var newref = get_unique_msgid({ deps: [tmaref], location: deps[oldref].location, type: deps[oldref].type }, deps); + numbers_del_oref(tmaroot, oldref); + numbers_add_oref(tmaroot, newref); + remap[oldref] = newref; + var msg = numbers_iwa_find(cfb, deps, oldref); + msg.id = newref; + if(deps[oldref].location == deps[tmaref].location) { + arch.push(msg); + } else { + deps[newref].location = deps[oldref].location.replace(oldref.toString(), newref.toString()); + if(deps[newref].location == deps[oldref].location) deps[newref].location = deps[newref].location.replace(/\.iwa/, `-${newref}.iwa`); + CFB.utils.cfb_add(cfb, deps[newref].location, compress_iwa_file(write_iwa_file([ msg ]))); + + var newloc = deps[newref].location; + newloc = newloc.replace(/^Root Entry\//,""); // NOTE: the Root Entry prefix is an artifact of the CFB container library + newloc = newloc.replace(/^Index\//, "").replace(/\.iwa$/,""); + + numbers_iwa_doit(cfb, deps, 2, (ai => { + var mlist = parse_shallow(ai.messages[0].data); + mlist[3].push({type: 2, data: write_shallow([ + [], + [{type: 0, data: write_varint49(newref)}], + [{type: 2, data: stru8(newloc.replace(/-.*$/, "")) }], + [{type: 2, data: stru8(newloc)}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [], + [], + [], + [], + [{type: 0, data: write_varint49(0)}], + [], + [{type: 0, data: write_varint49(0 /* TODO: save_token */)}], + ])}); + mlist[1] = [{type: 0, data: write_varint49(Math.max(newref + 1, parse_varint49(mlist[1][0].data) ))}]; + + /* add reference from TableModelArchive file to Tile */ + var parentidx = mlist[3].findIndex(m => { + var mm = parse_shallow(m.data); + if(mm[3]?.[0]) return u8str(mm[3][0].data) == loc; + if(mm[2]?.[0] && u8str(mm[2][0].data) == loc) return true; + return false; + }); + var parent = parse_shallow(mlist[3][parentidx].data); + if(!parent[6]) parent[6] = []; + parent[6].push({ + type: 2, + data: write_shallow([ + [], + [{type: 0, data: write_varint49(newref) }] + ]) + }); + mlist[3][parentidx].data = write_shallow(parent); + + ai.messages[0].data = write_shallow(mlist); + })); + } + tspref.data = write_TSP_Reference(newref); + }) + } + store[1][0].data = write_shallow(row_headers); + + /* copy tiles */ + var tiles = parse_shallow(store[3][0].data); + { + tiles[1].forEach(t => { + var tst = parse_shallow(t.data); + var oldtileref = parse_TSP_Reference(tst[2][0].data); + var newtileref = remap[oldtileref]; + if(!remap[oldtileref]) { + newtileref = get_unique_msgid({ deps: [tmaref], location: "", type: deps[oldtileref].type }, deps); + deps[newtileref].location = `Root Entry/Index/Tables/Tile-${newtileref}.iwa`; + remap[oldtileref] = newtileref; + + var oldtile = numbers_iwa_find(cfb, deps, oldtileref); + oldtile.id = newtileref; + numbers_del_oref(tmaroot, oldtileref); + numbers_add_oref(tmaroot, newtileref); + + CFB.utils.cfb_add(cfb, `/Index/Tables/Tile-${newtileref}.iwa`, compress_iwa_file(write_iwa_file([ oldtile ]))); + + numbers_iwa_doit(cfb, deps, 2, (ai => { + var mlist = parse_shallow(ai.messages[0].data); + mlist[3].push({type: 2, data: write_shallow([ + [], + [{type: 0, data: write_varint49(newtileref)}], + [{type: 2, data: stru8("Tables/Tile") }], + [{type: 2, data: stru8(`Tables/Tile-${newtileref}`)}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [{type: 2, data: new Uint8Array([2, 0, 0])}], + [], + [], + [], + [], + [{type: 0, data: write_varint49(0)}], + [], + [{type: 0, data: write_varint49(0 /* TODO: save_token */)}], + ])}); + mlist[1] = [{type: 0, data: write_varint49(Math.max(newtileref + 1, parse_varint49(mlist[1][0].data) ))}]; + + /* add reference from TableModelArchive file to Tile */ + var parentidx = mlist[3].findIndex(m => { + var mm = parse_shallow(m.data); + if(mm[3]?.[0]) return u8str(mm[3][0].data) == loc; + if(mm[2]?.[0] && u8str(mm[2][0].data) == loc) return true; + return false; + }); + var parent = parse_shallow(mlist[3][parentidx].data); + if(!parent[6]) parent[6] = []; + parent[6].push({ + type: 2, + data: write_shallow([ + [], + [{type: 0, data: write_varint49(newtileref) }] + ]) + }); + mlist[3][parentidx].data = write_shallow(parent); + + ai.messages[0].data = write_shallow(mlist); + })); + } + tst[2][0].data = write_TSP_Reference(newtileref); + t.data = write_shallow(tst); + }) + } + store[3][0].data = write_shallow(tiles); + } + tma[4][0].data = write_shallow(store); + tmaroot.messages[0].data = write_shallow(tma); + }); +} + /** Write NUMBERS worksheet */ function write_numbers_ws(cfb: CFB$Container, deps: Dependents, ws: WorkSheet, wsname: string, sheetidx: number, rootref: number): void { - /* TODO: support multiple worksheets, larger ranges, more data types, etc */ - if(sheetidx >= 1) return console.error("The Numbers writer currently writes only the first table"); + /* TODO: support more data types, etc */ /* .TN.SheetArchive */ var drawables: number[] = []; @@ -1064,7 +1479,7 @@ function write_numbers_ws(cfb: CFB$Container, deps: Dependents, ws: WorkSheet, w var USE_WIDE_ROWS = true; /** Write .TST.TableModelArchive */ -function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IWAArchiveInfo, tmafile: IWAArchiveInfo[], tmaref: number) { +function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws: WorkSheet, tmaroot: IWAArchiveInfo, tmafile: IWAArchiveInfo[], tmaref: number) { var range = decode_range(ws["!ref"] as string); range.s.r = range.s.c = 0; @@ -1128,6 +1543,7 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW { sstdata[3] = []; SST.forEach((str, i) => { + if(i == 0) return; // Numbers will assert if index zero sstdata[3].push({type: 2, data: write_shallow([ [], [ { type: 0, data: write_varint49(i) } ], [ { type: 0, data: write_varint49(1) } ], @@ -1145,12 +1561,21 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW var tilestore = parse_shallow(store[3][0].data); { /* number of rows per tile */ - var tstride = 256; // NOTE: if this is not 256, Numbers will recalculate + var tstride = 256; // NOTE: if this is not 256, Numbers will assert and recalculate tilestore[2] = [{type: 0, data: write_varint49(tstride)}]; //tilestore[3] = [{type: 0, data: write_varint49(USE_WIDE_ROWS ? 1 : 0)}]; // elicits a modification message var tileref = parse_TSP_Reference(parse_shallow(tilestore[1][0].data)[2][0].data); - var save_token = 0; + + /* save the save_token from package metadata */ + var save_token = ((): number => { + /* .TSP.PackageMetadata */ + var metadata = numbers_iwa_find(cfb, deps, 2); + var mlist = parse_shallow(metadata.messages[0].data); + /* .TSP.ComponentInfo field 1 is the id, field 12 is the save token */ + var mlst = mlist[3].filter(m => parse_varint49(parse_shallow(m.data)[1][0].data) == tileref); + return (mlst?.length) ? parse_varint49(parse_shallow(mlst[0].data)[12][0].data) : 0; + })(); /* remove existing tile */ { @@ -1160,8 +1585,6 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW numbers_iwa_doit(cfb, deps, 2, (ai => { var mlist = parse_shallow(ai.messages[0].data); - var lst = mlist[3].filter(m => parse_varint49(parse_shallow(m.data)[1][0].data) == tileref); - if(lst && lst.length > 0) save_token = parse_varint49(parse_shallow(lst[0].data)[12][0].data); mlist[3] = mlist[3].filter(m => parse_varint49(parse_shallow(m.data)[1][0].data) != tileref); /* remove reference from TableModelArchive file to Tile */ @@ -1178,6 +1601,8 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW ai.messages[0].data = write_shallow(mlist); })); + + numbers_del_oref(tmaroot, tileref); } /* rewrite entire tile storage */ @@ -1205,18 +1630,7 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW [{type: 0, data: write_varint49(USE_WIDE_ROWS ? 1 : 0)}] ]; for(var R = tidx * tstride; R <= Math.min(range.e.r, (tidx + 1) * tstride - 1); ++R) { - var tilerow: ProtoMessage = [ - [], - [ { type: 0, data: write_varint49(0) }], - [ { type: 0, data: write_varint49(0) }], - [ { type: 2, data: new Uint8Array([]) }], - [ { type: 2, data: new Uint8Array(Array.from({length:510}, () => 255)) }], - [ { type: 0, data: write_varint49(5) }], - [ { type: 2, data: new Uint8Array([]) }], - [ { type: 2, data: new Uint8Array(Array.from({length:510}, () => 255)) }], - [ { type: 0, data: write_varint49(1) }], - ] as ProtoMessage; - write_tile_row(tilerow, data[R], SST, USE_WIDE_ROWS); + var tilerow = write_TST_TileRowInfo(data[R], SST, USE_WIDE_ROWS); tilerow[1][0].data = write_varint49(R - tidx * tstride); tiledata[5].push({data: write_shallow(tilerow), type: 2}); } @@ -1278,12 +1692,7 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW })); /* add to TableModelArchive object references */ - var orefs: number[] = tmaroot.messages[0].meta[5]?.[0] ? parse_packed_varints(tmaroot.messages[0].meta[5][0].data) : []; - var orefidx = orefs.indexOf(newtileid); - if(orefidx == -1) { - orefs[orefidx = orefs.length] = newtileid; - tmaroot.messages[0].meta[5] =[ {type: 2, data: write_packed_varints(orefs) }]; - } + numbers_add_oref(tmaroot, newtileid); /* add to row rbtree */ rbtree[1].push({type: 2, data: write_shallow([ @@ -1345,12 +1754,7 @@ function write_numbers_tma(cfb: CFB$Container, deps: Dependents, ws, tmaroot: IW })); /* add object reference from TableModelArchive */ - /* var*/ orefs /*: number[]*/ = tmaroot.messages[0].meta[5]?.[0] ? parse_packed_varints(tmaroot.messages[0].meta[5][0].data) : []; - /* var*/ orefidx = orefs.indexOf(mergeid); - if(orefidx == -1) { - orefs[orefidx = orefs.length] = mergeid; - tmaroot.messages[0].meta[5] =[ {type: 2, data: write_packed_varints(orefs) }]; - } + numbers_add_oref(tmaroot, mergeid); } else delete store[13]; // TODO: delete references to merge if not needed