From 4743085cb51cad2b9f8b96a22fa9d359a4cf9fe8 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Thu, 30 Mar 2017 20:47:35 -0400 Subject: [PATCH] XLSB/XLSM sheet rels and hyperlinks - XLSB/XLSM workbook/worksheet code names for VBA - XLSX/XLSB write hyperlinks - updated CFB to 0.11.1 Fixes: - fixes #615 h/t @johnothetree (XLSM CodeName) - fixes #93 h/t @SheetJSDev (Write Hyperlinks) - fixes #156 h/t @MayaGi (XLSX Write Hyperlinks) - fixes #344 h/t @slonoed (XLSX Write Hyperlinks) --- Makefile | 2 + bits/18_cfb.js | 21 +++--- bits/28_binstructs.js | 9 +-- bits/31_rels.js | 17 ++++- bits/40_harb.js | 2 +- bits/67_wsxml.js | 27 +++++-- bits/68_wsbin.js | 41 +++++++++-- bits/72_wbxml.js | 2 +- bits/73_wbbin.js | 17 ++--- bits/74_xmlbin.js | 4 +- bits/76_xls.js | 2 +- bits/86_writezip.js | 19 ++--- package.json | 2 +- test.js | 4 +- xlsx.flow.js | 161 ++++++++++++++++++++++++++++-------------- xlsx.js | 161 ++++++++++++++++++++++++++++-------------- 16 files changed, 332 insertions(+), 159 deletions(-) diff --git a/Makefile b/Makefile index 097624a..abe0527 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ FLOWAUX=$(patsubst %.js,%.flow.js,$(AUXTARGETS)) AUXSCPTS=xlsxworker1.js xlsxworker2.js xlsxworker.js FLOWTGTS=$(TARGET) $(AUXTARGETS) $(AUXSCPTS) UGLIFYOPTS=--support-ie8 +CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar ## Main Targets @@ -144,6 +145,7 @@ lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks @jshint --show-non-errors package.json bower.json @jshint --show-non-errors --extract=always $(HTMLLINT) @jscs $(TARGET) $(AUXTARGETS) + if [ -e $(CLOSURE) ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi .PHONY: flow flow: lint ## Run flow checker diff --git a/bits/18_cfb.js b/bits/18_cfb.js index 2bd8cb5..c5b75e1 100644 --- a/bits/18_cfb.js +++ b/bits/18_cfb.js @@ -30,7 +30,7 @@ type SectorList = { /* [MS-CFB] v20130118 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '0.11.0'; +exports.version = '0.11.1'; function parse(file) { var mver = 3; // major version var ssz = 512; // sector size @@ -51,7 +51,7 @@ var mv = check_get_mver(blob); mver = mv[0]; switch(mver) { case 3: ssz = 512; break; case 4: ssz = 4096; break; - default: throw "Major Version: Expected 3 or 4 saw " + mver; + default: throw new Error("Major Version: Expected 3 or 4 saw " + mver); } /* reprocess header */ @@ -63,7 +63,7 @@ check_shifts(blob, mver); // Number of Directory Sectors var nds = blob.read_shift(4, 'i'); -if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; +if(mver === 3 && nds !== 0) throw new Error('# Directory Sectors: Expected 0 saw ' + nds); // Number of FAT Sectors //var nfs = blob.read_shift(4, 'i'); @@ -149,13 +149,14 @@ function check_shifts(blob, mver) { var shift = 0x09; // Byte Order - blob.chk('feff', 'Byte Order: '); + //blob.chk('feff', 'Byte Order: '); // note: some writers put 0xffff + blob.l += 2; // Sector Shift switch((shift = blob.read_shift(2))) { - case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; - case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; - default: throw 'Sector Shift: Expected 9 or 12 saw ' + shift; + case 0x09: if(mver != 3) throw new Error('Sector Shift: Expected 9 saw ' + shift); break; + case 0x0c: if(mver != 4) throw new Error('Sector Shift: Expected 12 saw ' + shift); break; + default: throw new Error('Sector Shift: Expected 9 or 12 saw ' + shift); } // Mini Sector Shift @@ -237,7 +238,7 @@ function make_find_path(FullPaths, Paths, FileIndex, files, root_name) { function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) { var q; if(idx === ENDOFCHAIN) { - if(cnt !== 0) throw "DIFAT chain shorter than expected"; + if(cnt !== 0) throw new Error("DIFAT chain shorter than expected"); } else if(idx !== -1 /*FREESECT*/) { var sector = sectors[idx], m = (ssz>>>2)-1; if(!sector) return; @@ -263,7 +264,7 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) { buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } @@ -286,7 +287,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz/*:number*/)/*:any*/ buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } diff --git a/bits/28_binstructs.js b/bits/28_binstructs.js index e3570e2..3adca12 100644 --- a/bits/28_binstructs.js +++ b/bits/28_binstructs.js @@ -18,9 +18,9 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ { z.r = rgsStrRun; } else z.r = "" + escapexml(str) + ""; - if((flags & 2) !== 0) { /* fExtStr */ - /* TODO: phonetic string */ - } + //if((flags & 2) !== 0) { /* fExtStr */ + // /* TODO: phonetic string */ + //} data.l = start + length; return z; } @@ -50,7 +50,8 @@ function write_XLSBCell(cell/*:any*/, o/*:?Block*/) { /* [MS-XLSB] 2.5.21 */ -function parse_XLSBCodeName (data, length) { return parse_XLWideString(data, length); } +var parse_XLSBCodeName = parse_XLWideString; +var write_XLSBCodeName = write_XLWideString; /* [MS-XLSB] 2.5.166 */ function parse_XLNullableWideString(data)/*:string*/ { diff --git a/bits/31_rels.js b/bits/31_rels.js index cff1f88..4e4d4e2 100644 --- a/bits/31_rels.js +++ b/bits/31_rels.js @@ -2,13 +2,14 @@ var RELS = ({ WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + HLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject" }/*:any*/); /* 9.3.3 Representing Relationships */ function get_rels_path(file/*:string*/)/*:string*/ { var n = file.lastIndexOf("/"); - return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels"; + return file.substr(0,n+1) + '_rels/' + file.substr(n+1) + ".rels"; } function parse_rels(data/*:?string*/, currentFilePath/*:string*/) { @@ -51,3 +52,17 @@ function write_rels(rels)/*:string*/ { if(o.length>2){ o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } + +function add_rels(rels, rId, f, type, relobj)/*:number*/ { + if(!relobj) relobj = {}; + if(!rels['!id']) rels['!id'] = {}; + if(rId < 0) for(rId = 1; rels['!id']['rId' + rId]; ++rId){} + relobj.Id = 'rId' + rId; + relobj.Type = type; + relobj.Target = f; + if(relobj.Type == RELS.HLINK) relobj.TargetMode = "External"; + if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); + rels['!id'][relobj.Id] = relobj; + rels[('/' + relobj.Target).replace("//","/")] = relobj; + return rId; +} diff --git a/bits/40_harb.js b/bits/40_harb.js index d6595d5..59fd729 100644 --- a/bits/40_harb.js +++ b/bits/40_harb.js @@ -63,7 +63,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ { case 0x83: memo = true; break; case 0x8B: memo = true; break; case 0xF5: memo = true; break; - default: process.exit(); throw new Error("DBF Unsupported Version: " + ft.toString(16)); + default: throw new Error("DBF Unsupported Version: " + ft.toString(16)); } var filedate = new Date(d.read_shift(1) + 1900, d.read_shift(1) - 1, d.read_shift(1)); var nrow = d.read_shift(4); diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index 919449e..81a9c4d 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -155,6 +155,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { var ff = cell.F && cell.F.substr(0, ref.length) == ref ? {t:"array", ref:cell.F} : null; v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : ""); } + if(cell.l) ws['!links'].push([ref, cell.l]); return writextag('c', v, o); } @@ -290,7 +291,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } }; })(); -function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { +function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/, rels)/*:string*/ { var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R=0, C=0; for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C); for(R = range.s.r; R <= range.e.r; ++R) { @@ -311,26 +312,42 @@ var WS_XML_ROOT = writextag('worksheet', null, { 'xmlns:r': XMLNS.r }); -function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ { +function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { var o = [XML_HEADER, WS_XML_ROOT]; var s = wb.SheetNames[idx], sidx = 0, rdata = ""; var ws = wb.Sheets[s]; if(ws === undefined) ws = {}; var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; + if(!rels) rels = {}; + + o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); o[o.length] = (writextag('dimension', null, {'ref': ref})); if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); o[sidx = o.length] = ''; + ws['!links'] = []; if(ws['!ref'] != null) { - rdata = write_ws_xml_data(ws, opts, idx, wb); + rdata = write_ws_xml_data(ws, opts, idx, wb, rels); if(rdata.length > 0) o[o.length] = (rdata); } if(o.length>sidx+1) { o[o.length] = (''); o[sidx]=o[sidx].replace("/>",">"); } if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); - if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } - + var relc = -1, rel; + if(ws['!links'].length > 0) { + o[o.length] = ""; + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK); + rel = ({"ref":l[0], "r:id":"rId"+rId}/*:any*/); + if((relc = l[1].Target.indexOf("#")) > -1) rel.location = escapexml(l[1].Target.substr(relc+1)); + if(l[1].Tooltip) rel.tooltip = escapexml(l[1].Tooltip); + o[o.length] = writextag("hyperlink",null,rel); + }); + o[o.length] = ""; + } delete ws['!links']; + if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index bda5cec..2a2b271 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -58,6 +58,14 @@ function parse_BrtWsProp(data, length) { z.name = parse_XLSBCodeName(data, length - 19); return z; } +function write_BrtWsProp(str, o) { + if(o == null) o = new_buf(80+4*str.length); + for(var i = 0; i < 11; ++i) o.write_shift(1,0); + o.write_shift(-4,-1); + o.write_shift(-4,-1); + write_XLSBCodeName(str, o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.303 BrtCellBlank */ function parse_BrtCellBlank(data, length) { @@ -228,6 +236,17 @@ function parse_BrtHLink(data, length, opts) { data.l = end; return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display}; } +function write_BrtHLink(l, rId, o) { + if(o == null) o = new_buf(50+4*l[1].Target.length); + write_UncheckedRfX({s:decode_cell(l[0]), e:decode_cell(l[0])}, o); + write_RelID("rId" + rId, o); + var locidx = l[1].Target.indexOf("#"); + var location = locidx == -1 ? "" : l[1].Target.substr(locidx+1); + write_XLWideString(location || "", o); + write_XLWideString(l[1].Tooltip || "", o); + write_XLWideString("", o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.6 BrtArrFmla */ function parse_BrtArrFmla(data, length, opts) { @@ -533,7 +552,7 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ { } /* TODO: something useful -- this is a stub */ -function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) { +function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts, ws/*:Worksheet*/) { if(cell.v === undefined) return ""; var vv = ""; var olddate = null; switch(cell.t) { @@ -550,6 +569,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num var o/*:any*/ = ({r:R, c:C}/*:any*/); /* TODO: cell style */ //o.s = get_cell_style(opts.cellXfs, cell, opts); + if(cell.l) ws['!links'].push([encode_cell(o), cell.l]); switch(cell.t) { case 's': case 'str': if(opts.bookSST) { @@ -590,7 +610,7 @@ function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbo ref = cols[C] + rr; if(!ws[ref]) continue; /* write cell */ - write_ws_bin_cell(ba, ws[ref], R, C, opts); + write_ws_bin_cell(ba, ws[ref], R, C, opts, ws); } } write_record(ba, 'BrtEndSheetData'); @@ -610,12 +630,23 @@ function write_COLINFOS(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workboo write_record(ba, 'BrtEndColInfos'); } -function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) { +function write_HLINKS(ba, ws/*:Worksheet*/, rels) { + /* *BrtHLink */ + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, l[1].Target.replace(/#.*$/, ""), RELS.HLINK); + write_record(ba, "BrtHLink", write_BrtHLink(l, rId)); + }); + delete ws['!links']; +} + +function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { var ba = buf_array(); var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; var r = safe_decode_range(ws['!ref'] || "A1"); + ws['!links'] = []; write_record(ba, "BrtBeginSheet"); - /* [BrtWsProp] */ + write_record(ba, "BrtWsProp", write_BrtWsProp(s)); write_record(ba, "BrtWsDim", write_BrtWsDim(r)); /* [WSVIEWS2] */ /* [WSFMTINFO] */ @@ -633,7 +664,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) { /* [BrtPhoneticInfo] */ /* *CONDITIONALFORMATTING */ /* [DVALS] */ - /* *BrtHLink */ + write_HLINKS(ba, ws, rels); /* [BrtPrintOptions] */ /* [BrtMargins] */ /* [BrtPageSetup] */ diff --git a/bits/72_wbxml.js b/bits/72_wbxml.js index 2c1e134..991d04c 100644 --- a/bits/72_wbxml.js +++ b/bits/72_wbxml.js @@ -151,7 +151,7 @@ function safe1904(wb/*:Workbook*/)/*:string*/ { function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { var o = [XML_HEADER]; o[o.length] = WB_XML_ROOT; - o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb)})); + o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); o[o.length] = ""; for(var i = 0; i != wb.SheetNames.length; ++i) o[o.length] = (writextag('sheet',null,{name:escapexml(wb.SheetNames[i].substr(0,31)), sheetId:""+(i+1), "r:id":"rId"+(i+1)})); diff --git a/bits/73_wbbin.js b/bits/73_wbbin.js index a86b56c..57c9594 100644 --- a/bits/73_wbbin.js +++ b/bits/73_wbbin.js @@ -24,10 +24,11 @@ function parse_BrtWbProp(data, length) { return [dwThemeVersion, strName]; } function write_BrtWbProp(data, o) { - if(!o) o = new_buf(8); + if(!o) o = new_buf(68); o.write_shift(4, 0); o.write_shift(4, 0); - return o; + write_XLSBCodeName("ThisWorkbook", o); + return o.slice(0, o.l); } function parse_BrtFRTArchID$(data, length) { @@ -47,12 +48,12 @@ function parse_BrtName(data, length, opts) { var name = parse_XLNameWideString(data); var formula = parse_XLSBNameParsedFormula(data, 0, opts); var comment = parse_XLNullableWideString(data); - if(0 /* fProc */) { + //if(0 /* fProc */) { // unusedstring1: XLNullableWideString // description: XLNullableWideString // helpTopic: XLNullableWideString // unusedstring2: XLNullableWideString - } + //} data.l = end; return {Name:name, Ptg:formula, Comment:comment}; } @@ -188,22 +189,22 @@ function write_wb_bin(wb, opts) { write_record(ba, "BrtBeginBook"); write_record(ba, "BrtFileVersion", write_BrtFileVersion()); /* [[BrtFileSharingIso] BrtFileSharing] */ - if(0) write_record(ba, "BrtWbProp", write_BrtWbProp()); + write_record(ba, "BrtWbProp", write_BrtWbProp()); /* [ACABSPATH] */ /* [[BrtBookProtectionIso] BrtBookProtection] */ - if(0) write_BOOKVIEWS(ba, wb, opts); + /* write_BOOKVIEWS(ba, wb, opts); */ write_BUNDLESHS(ba, wb, opts); /* [FNGROUP] */ /* [EXTERNALS] */ /* *BrtName */ - if(0) write_record(ba, "BrtCalcProp", write_BrtCalcProp()); + /* write_record(ba, "BrtCalcProp", write_BrtCalcProp()); */ /* [BrtOleSize] */ /* *(BrtUserBookView *FRT) */ /* [PIVOTCACHEIDS] */ /* [BrtWbFactoid] */ /* [SMARTTAGTYPES] */ /* [BrtWebOpt] */ - if(0) write_record(ba, "BrtFileRecover", write_BrtFileRecover()); + /* write_record(ba, "BrtFileRecover", write_BrtFileRecover()); */ /* [WEBPUBITEMS] */ /* [CRERRS] */ /* FRTWORKBOOK */ diff --git a/bits/74_xmlbin.js b/bits/74_xmlbin.js index 852e1d9..5cee155 100644 --- a/bits/74_xmlbin.js +++ b/bits/74_xmlbin.js @@ -51,8 +51,8 @@ function write_wb(wb, name/*:string*/, opts) { return (name.slice(-4)===".bin" ? write_wb_bin : write_wb_xml)(wb, opts); } -function write_ws(data/*:Worksheet*/, name/*:string*/, opts, wb/*:Workbook*/) { - return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb); +function write_ws(data/*:Worksheet*/, name/*:string*/, opts, wb/*:Workbook*/, rels) { + return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb, rels); } function write_sty(data, name/*:string*/, opts) { diff --git a/bits/76_xls.js b/bits/76_xls.js index 1aba939..b6458af 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -58,7 +58,7 @@ function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; - if(p.t === 'e'); + if(p.t === 'e'){} else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); diff --git a/bits/86_writezip.js b/bits/86_writezip.js index 9217ea4..cce4358 100644 --- a/bits/86_writezip.js +++ b/bits/86_writezip.js @@ -1,14 +1,3 @@ -function add_rels(rels, rId, f, type, relobj) { - if(!relobj) relobj = {}; - if(!rels['!id']) rels['!id'] = {}; - relobj.Id = 'rId' + rId; - relobj.Type = type; - relobj.Target = f; - if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); - rels['!id'][relobj.Id] = relobj; - rels[('/' + relobj.Target).replace("//","/")] = relobj; -} - function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { if(opts.bookType == "ods") return write_ods(wb, opts); if(wb && !wb.SSF) { @@ -64,9 +53,11 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { for(rId=1;rId <= wb.SheetNames.length; ++rId) { f = "xl/worksheets/sheet" + rId + "." + wbext; - zip.file(f, write_ws(rId-1, f, opts, wb)); + var wsrels = {'!id':{}}; + zip.file(f, write_ws(rId-1, f, opts, wb, wsrels)); ct.sheets.push(f); add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]); + if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels)); // get_rels_path('') } if(opts.Strings != null && opts.Strings.length > 0) { @@ -98,7 +89,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { } zip.file("[Content_Types].xml", write_ct(ct, opts)); - zip.file('_rels/.rels', write_rels(opts.rels)); - zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); + zip.file('_rels/.rels', write_rels(opts.rels)); // get_rels_path('') + zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); // get_rels_path("xl/workbook." + wbext) return zip; } diff --git a/package.json b/package.json index 1fed0a8..6dfd793 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "exit-on-epipe":"~1.0.0", "ssf":"~0.9.0", "codepage":"~1.8.0", - "cfb":"~0.11.0", + "cfb":"~0.11.1", "crc-32":"~1.0.0", "adler-32":"~1.0.0", "commander":"~2.9.0" diff --git a/test.js b/test.js index 65563a9..8f6df50 100644 --- a/test.js +++ b/test.js @@ -989,8 +989,8 @@ describe('roundtrip features', function() { describe('should preserve hyperlink', function() { [ ['xlml', paths.hlxml], - //['xlsx', paths.hlxlsx], // TODO - //['xlsb', paths.hlxlsb] // TODO + ['xlsx', paths.hlxlsx], + ['xlsb', paths.hlxlsb] ].forEach(function(w) { it(w[0], function() { var wb1 = X.readFile(w[1]); diff --git a/xlsx.flow.js b/xlsx.flow.js index bda8f26..d3c9c34 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -987,7 +987,7 @@ type SectorList = { /* [MS-CFB] v20130118 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '0.11.0'; +exports.version = '0.11.1'; function parse(file) { var mver = 3; // major version var ssz = 512; // sector size @@ -1008,7 +1008,7 @@ var mv = check_get_mver(blob); mver = mv[0]; switch(mver) { case 3: ssz = 512; break; case 4: ssz = 4096; break; - default: throw "Major Version: Expected 3 or 4 saw " + mver; + default: throw new Error("Major Version: Expected 3 or 4 saw " + mver); } /* reprocess header */ @@ -1020,7 +1020,7 @@ check_shifts(blob, mver); // Number of Directory Sectors var nds = blob.read_shift(4, 'i'); -if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; +if(mver === 3 && nds !== 0) throw new Error('# Directory Sectors: Expected 0 saw ' + nds); // Number of FAT Sectors //var nfs = blob.read_shift(4, 'i'); @@ -1106,13 +1106,14 @@ function check_shifts(blob, mver) { var shift = 0x09; // Byte Order - blob.chk('feff', 'Byte Order: '); + //blob.chk('feff', 'Byte Order: '); // note: some writers put 0xffff + blob.l += 2; // Sector Shift switch((shift = blob.read_shift(2))) { - case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; - case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; - default: throw 'Sector Shift: Expected 9 or 12 saw ' + shift; + case 0x09: if(mver != 3) throw new Error('Sector Shift: Expected 9 saw ' + shift); break; + case 0x0c: if(mver != 4) throw new Error('Sector Shift: Expected 12 saw ' + shift); break; + default: throw new Error('Sector Shift: Expected 9 or 12 saw ' + shift); } // Mini Sector Shift @@ -1194,7 +1195,7 @@ function make_find_path(FullPaths, Paths, FileIndex, files, root_name) { function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) { var q; if(idx === ENDOFCHAIN) { - if(cnt !== 0) throw "DIFAT chain shorter than expected"; + if(cnt !== 0) throw new Error("DIFAT chain shorter than expected"); } else if(idx !== -1 /*FREESECT*/) { var sector = sectors[idx], m = (ssz>>>2)-1; if(!sector) return; @@ -1220,7 +1221,7 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) { buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } @@ -1243,7 +1244,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz/*:number*/)/*:any*/ buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } @@ -2129,9 +2130,9 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ { z.r = rgsStrRun; } else z.r = "" + escapexml(str) + ""; - if((flags & 2) !== 0) { /* fExtStr */ - /* TODO: phonetic string */ - } + //if((flags & 2) !== 0) { /* fExtStr */ + // /* TODO: phonetic string */ + //} data.l = start + length; return z; } @@ -2161,7 +2162,8 @@ function write_XLSBCell(cell/*:any*/, o/*:?Block*/) { /* [MS-XLSB] 2.5.21 */ -function parse_XLSBCodeName (data, length) { return parse_XLWideString(data, length); } +var parse_XLSBCodeName = parse_XLWideString; +var write_XLSBCodeName = write_XLWideString; /* [MS-XLSB] 2.5.166 */ function parse_XLNullableWideString(data)/*:string*/ { @@ -2838,13 +2840,14 @@ function write_ct(ct, opts)/*:string*/ { var RELS = ({ WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + HLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject" }/*:any*/); /* 9.3.3 Representing Relationships */ function get_rels_path(file/*:string*/)/*:string*/ { var n = file.lastIndexOf("/"); - return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels"; + return file.substr(0,n+1) + '_rels/' + file.substr(n+1) + ".rels"; } function parse_rels(data/*:?string*/, currentFilePath/*:string*/) { @@ -2887,6 +2890,20 @@ function write_rels(rels)/*:string*/ { if(o.length>2){ o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } + +function add_rels(rels, rId, f, type, relobj)/*:number*/ { + if(!relobj) relobj = {}; + if(!rels['!id']) rels['!id'] = {}; + if(rId < 0) for(rId = 1; rels['!id']['rId' + rId]; ++rId){} + relobj.Id = 'rId' + rId; + relobj.Type = type; + relobj.Target = f; + if(relobj.Type == RELS.HLINK) relobj.TargetMode = "External"; + if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); + rels['!id'][relobj.Id] = relobj; + rels[('/' + relobj.Target).replace("//","/")] = relobj; + return rId; +} /* Open Document Format for Office Applications (OpenDocument) Version 1.2 */ /* Part 3 Section 4 Manifest File */ var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet"; @@ -4794,7 +4811,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ { case 0x83: memo = true; break; case 0x8B: memo = true; break; case 0xF5: memo = true; break; - default: process.exit(); throw new Error("DBF Unsupported Version: " + ft.toString(16)); + default: throw new Error("DBF Unsupported Version: " + ft.toString(16)); } var filedate = new Date(d.read_shift(1) + 1900, d.read_shift(1) - 1, d.read_shift(1)); var nrow = d.read_shift(4); @@ -9039,6 +9056,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { var ff = cell.F && cell.F.substr(0, ref.length) == ref ? {t:"array", ref:cell.F} : null; v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : ""); } + if(cell.l) ws['!links'].push([ref, cell.l]); return writextag('c', v, o); } @@ -9174,7 +9192,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } }; })(); -function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { +function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/, rels)/*:string*/ { var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R=0, C=0; for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C); for(R = range.s.r; R <= range.e.r; ++R) { @@ -9195,27 +9213,43 @@ var WS_XML_ROOT = writextag('worksheet', null, { 'xmlns:r': XMLNS.r }); -function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ { +function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { var o = [XML_HEADER, WS_XML_ROOT]; var s = wb.SheetNames[idx], sidx = 0, rdata = ""; var ws = wb.Sheets[s]; if(ws === undefined) ws = {}; var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; + if(!rels) rels = {}; + + o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); o[o.length] = (writextag('dimension', null, {'ref': ref})); if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); o[sidx = o.length] = ''; + ws['!links'] = []; if(ws['!ref'] != null) { - rdata = write_ws_xml_data(ws, opts, idx, wb); + rdata = write_ws_xml_data(ws, opts, idx, wb, rels); if(rdata.length > 0) o[o.length] = (rdata); } if(o.length>sidx+1) { o[o.length] = (''); o[sidx]=o[sidx].replace("/>",">"); } if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); - if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } - + var relc = -1, rel; + if(ws['!links'].length > 0) { + o[o.length] = ""; + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK); + rel = ({"ref":l[0], "r:id":"rId"+rId}/*:any*/); + if((relc = l[1].Target.indexOf("#")) > -1) rel.location = escapexml(l[1].Target.substr(relc+1)); + if(l[1].Tooltip) rel.tooltip = escapexml(l[1].Tooltip); + o[o.length] = writextag("hyperlink",null,rel); + }); + o[o.length] = ""; + } delete ws['!links']; + if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } @@ -9278,6 +9312,14 @@ function parse_BrtWsProp(data, length) { z.name = parse_XLSBCodeName(data, length - 19); return z; } +function write_BrtWsProp(str, o) { + if(o == null) o = new_buf(80+4*str.length); + for(var i = 0; i < 11; ++i) o.write_shift(1,0); + o.write_shift(-4,-1); + o.write_shift(-4,-1); + write_XLSBCodeName(str, o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.303 BrtCellBlank */ function parse_BrtCellBlank(data, length) { @@ -9448,6 +9490,17 @@ function parse_BrtHLink(data, length, opts) { data.l = end; return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display}; } +function write_BrtHLink(l, rId, o) { + if(o == null) o = new_buf(50+4*l[1].Target.length); + write_UncheckedRfX({s:decode_cell(l[0]), e:decode_cell(l[0])}, o); + write_RelID("rId" + rId, o); + var locidx = l[1].Target.indexOf("#"); + var location = locidx == -1 ? "" : l[1].Target.substr(locidx+1); + write_XLWideString(location || "", o); + write_XLWideString(l[1].Tooltip || "", o); + write_XLWideString("", o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.6 BrtArrFmla */ function parse_BrtArrFmla(data, length, opts) { @@ -9753,7 +9806,7 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ { } /* TODO: something useful -- this is a stub */ -function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) { +function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts, ws/*:Worksheet*/) { if(cell.v === undefined) return ""; var vv = ""; var olddate = null; switch(cell.t) { @@ -9770,6 +9823,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num var o/*:any*/ = ({r:R, c:C}/*:any*/); /* TODO: cell style */ //o.s = get_cell_style(opts.cellXfs, cell, opts); + if(cell.l) ws['!links'].push([encode_cell(o), cell.l]); switch(cell.t) { case 's': case 'str': if(opts.bookSST) { @@ -9810,7 +9864,7 @@ function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbo ref = cols[C] + rr; if(!ws[ref]) continue; /* write cell */ - write_ws_bin_cell(ba, ws[ref], R, C, opts); + write_ws_bin_cell(ba, ws[ref], R, C, opts, ws); } } write_record(ba, 'BrtEndSheetData'); @@ -9830,12 +9884,23 @@ function write_COLINFOS(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workboo write_record(ba, 'BrtEndColInfos'); } -function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) { +function write_HLINKS(ba, ws/*:Worksheet*/, rels) { + /* *BrtHLink */ + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, l[1].Target.replace(/#.*$/, ""), RELS.HLINK); + write_record(ba, "BrtHLink", write_BrtHLink(l, rId)); + }); + delete ws['!links']; +} + +function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { var ba = buf_array(); var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; var r = safe_decode_range(ws['!ref'] || "A1"); + ws['!links'] = []; write_record(ba, "BrtBeginSheet"); - /* [BrtWsProp] */ + write_record(ba, "BrtWsProp", write_BrtWsProp(s)); write_record(ba, "BrtWsDim", write_BrtWsDim(r)); /* [WSVIEWS2] */ /* [WSFMTINFO] */ @@ -9853,7 +9918,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) { /* [BrtPhoneticInfo] */ /* *CONDITIONALFORMATTING */ /* [DVALS] */ - /* *BrtHLink */ + write_HLINKS(ba, ws, rels); /* [BrtPrintOptions] */ /* [BrtMargins] */ /* [BrtPageSetup] */ @@ -10224,7 +10289,7 @@ function safe1904(wb/*:Workbook*/)/*:string*/ { function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { var o = [XML_HEADER]; o[o.length] = WB_XML_ROOT; - o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb)})); + o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); o[o.length] = ""; for(var i = 0; i != wb.SheetNames.length; ++i) o[o.length] = (writextag('sheet',null,{name:escapexml(wb.SheetNames[i].substr(0,31)), sheetId:""+(i+1), "r:id":"rId"+(i+1)})); @@ -10258,10 +10323,11 @@ function parse_BrtWbProp(data, length) { return [dwThemeVersion, strName]; } function write_BrtWbProp(data, o) { - if(!o) o = new_buf(8); + if(!o) o = new_buf(68); o.write_shift(4, 0); o.write_shift(4, 0); - return o; + write_XLSBCodeName("ThisWorkbook", o); + return o.slice(0, o.l); } function parse_BrtFRTArchID$(data, length) { @@ -10281,12 +10347,12 @@ function parse_BrtName(data, length, opts) { var name = parse_XLNameWideString(data); var formula = parse_XLSBNameParsedFormula(data, 0, opts); var comment = parse_XLNullableWideString(data); - if(0 /* fProc */) { + //if(0 /* fProc */) { // unusedstring1: XLNullableWideString // description: XLNullableWideString // helpTopic: XLNullableWideString // unusedstring2: XLNullableWideString - } + //} data.l = end; return {Name:name, Ptg:formula, Comment:comment}; } @@ -10422,22 +10488,22 @@ function write_wb_bin(wb, opts) { write_record(ba, "BrtBeginBook"); write_record(ba, "BrtFileVersion", write_BrtFileVersion()); /* [[BrtFileSharingIso] BrtFileSharing] */ - if(0) write_record(ba, "BrtWbProp", write_BrtWbProp()); + write_record(ba, "BrtWbProp", write_BrtWbProp()); /* [ACABSPATH] */ /* [[BrtBookProtectionIso] BrtBookProtection] */ - if(0) write_BOOKVIEWS(ba, wb, opts); + /* write_BOOKVIEWS(ba, wb, opts); */ write_BUNDLESHS(ba, wb, opts); /* [FNGROUP] */ /* [EXTERNALS] */ /* *BrtName */ - if(0) write_record(ba, "BrtCalcProp", write_BrtCalcProp()); + /* write_record(ba, "BrtCalcProp", write_BrtCalcProp()); */ /* [BrtOleSize] */ /* *(BrtUserBookView *FRT) */ /* [PIVOTCACHEIDS] */ /* [BrtWbFactoid] */ /* [SMARTTAGTYPES] */ /* [BrtWebOpt] */ - if(0) write_record(ba, "BrtFileRecover", write_BrtFileRecover()); + /* write_record(ba, "BrtFileRecover", write_BrtFileRecover()); */ /* [WEBPUBITEMS] */ /* [CRERRS] */ /* FRTWORKBOOK */ @@ -10498,8 +10564,8 @@ function write_wb(wb, name/*:string*/, opts) { return (name.slice(-4)===".bin" ? write_wb_bin : write_wb_xml)(wb, opts); } -function write_ws(data/*:Worksheet*/, name/*:string*/, opts, wb/*:Workbook*/) { - return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb); +function write_ws(data/*:Worksheet*/, name/*:string*/, opts, wb/*:Workbook*/, rels) { + return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb, rels); } function write_sty(data, name/*:string*/, opts) { @@ -11460,7 +11526,7 @@ function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; - if(p.t === 'e'); + if(p.t === 'e'){} else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); @@ -14309,17 +14375,6 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ { } return out; } -function add_rels(rels, rId, f, type, relobj) { - if(!relobj) relobj = {}; - if(!rels['!id']) rels['!id'] = {}; - relobj.Id = 'rId' + rId; - relobj.Type = type; - relobj.Target = f; - if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); - rels['!id'][relobj.Id] = relobj; - rels[('/' + relobj.Target).replace("//","/")] = relobj; -} - function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { if(opts.bookType == "ods") return write_ods(wb, opts); if(wb && !wb.SSF) { @@ -14375,9 +14430,11 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { for(rId=1;rId <= wb.SheetNames.length; ++rId) { f = "xl/worksheets/sheet" + rId + "." + wbext; - zip.file(f, write_ws(rId-1, f, opts, wb)); + var wsrels = {'!id':{}}; + zip.file(f, write_ws(rId-1, f, opts, wb, wsrels)); ct.sheets.push(f); add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]); + if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels)); // get_rels_path('') } if(opts.Strings != null && opts.Strings.length > 0) { @@ -14409,8 +14466,8 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { } zip.file("[Content_Types].xml", write_ct(ct, opts)); - zip.file('_rels/.rels', write_rels(opts.rels)); - zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); + zip.file('_rels/.rels', write_rels(opts.rels)); // get_rels_path('') + zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); // get_rels_path("xl/workbook." + wbext) return zip; } function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array*/ { diff --git a/xlsx.js b/xlsx.js index 0b124e5..b153435 100644 --- a/xlsx.js +++ b/xlsx.js @@ -939,7 +939,7 @@ var DO_NOT_EXPORT_CFB = true; /* [MS-CFB] v20130118 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '0.11.0'; +exports.version = '0.11.1'; function parse(file) { var mver = 3; // major version var ssz = 512; // sector size @@ -960,7 +960,7 @@ var mv = check_get_mver(blob); mver = mv[0]; switch(mver) { case 3: ssz = 512; break; case 4: ssz = 4096; break; - default: throw "Major Version: Expected 3 or 4 saw " + mver; + default: throw new Error("Major Version: Expected 3 or 4 saw " + mver); } /* reprocess header */ @@ -972,7 +972,7 @@ check_shifts(blob, mver); // Number of Directory Sectors var nds = blob.read_shift(4, 'i'); -if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; +if(mver === 3 && nds !== 0) throw new Error('# Directory Sectors: Expected 0 saw ' + nds); // Number of FAT Sectors //var nfs = blob.read_shift(4, 'i'); @@ -1058,13 +1058,14 @@ function check_shifts(blob, mver) { var shift = 0x09; // Byte Order - blob.chk('feff', 'Byte Order: '); + //blob.chk('feff', 'Byte Order: '); // note: some writers put 0xffff + blob.l += 2; // Sector Shift switch((shift = blob.read_shift(2))) { - case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; - case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; - default: throw 'Sector Shift: Expected 9 or 12 saw ' + shift; + case 0x09: if(mver != 3) throw new Error('Sector Shift: Expected 9 saw ' + shift); break; + case 0x0c: if(mver != 4) throw new Error('Sector Shift: Expected 12 saw ' + shift); break; + default: throw new Error('Sector Shift: Expected 9 or 12 saw ' + shift); } // Mini Sector Shift @@ -1146,7 +1147,7 @@ function make_find_path(FullPaths, Paths, FileIndex, files, root_name) { function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) { var q; if(idx === ENDOFCHAIN) { - if(cnt !== 0) throw "DIFAT chain shorter than expected"; + if(cnt !== 0) throw new Error("DIFAT chain shorter than expected"); } else if(idx !== -1 /*FREESECT*/) { var sector = sectors[idx], m = (ssz>>>2)-1; if(!sector) return; @@ -1172,7 +1173,7 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) { buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } @@ -1195,7 +1196,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz) { buf_chain.push(sectors[j]); var addr = fat_addrs[Math.floor(j*4/ssz)]; jj = ((j*4) & modulus); - if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz); if(!sectors[addr]) break; j = __readInt32LE(sectors[addr], jj); } @@ -2077,9 +2078,9 @@ function parse_RichStr(data, length) { z.r = rgsStrRun; } else z.r = "" + escapexml(str) + ""; - if((flags & 2) !== 0) { /* fExtStr */ - /* TODO: phonetic string */ - } + //if((flags & 2) !== 0) { /* fExtStr */ + // /* TODO: phonetic string */ + //} data.l = start + length; return z; } @@ -2109,7 +2110,8 @@ function write_XLSBCell(cell, o) { /* [MS-XLSB] 2.5.21 */ -function parse_XLSBCodeName (data, length) { return parse_XLWideString(data, length); } +var parse_XLSBCodeName = parse_XLWideString; +var write_XLSBCodeName = write_XLWideString; /* [MS-XLSB] 2.5.166 */ function parse_XLNullableWideString(data) { @@ -2786,13 +2788,14 @@ function write_ct(ct, opts) { var RELS = ({ WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + HLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject" }); /* 9.3.3 Representing Relationships */ function get_rels_path(file) { var n = file.lastIndexOf("/"); - return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels"; + return file.substr(0,n+1) + '_rels/' + file.substr(n+1) + ".rels"; } function parse_rels(data, currentFilePath) { @@ -2835,6 +2838,20 @@ function write_rels(rels) { if(o.length>2){ o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } + +function add_rels(rels, rId, f, type, relobj) { + if(!relobj) relobj = {}; + if(!rels['!id']) rels['!id'] = {}; + if(rId < 0) for(rId = 1; rels['!id']['rId' + rId]; ++rId){} + relobj.Id = 'rId' + rId; + relobj.Type = type; + relobj.Target = f; + if(relobj.Type == RELS.HLINK) relobj.TargetMode = "External"; + if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); + rels['!id'][relobj.Id] = relobj; + rels[('/' + relobj.Target).replace("//","/")] = relobj; + return rId; +} /* Open Document Format for Office Applications (OpenDocument) Version 1.2 */ /* Part 3 Section 4 Manifest File */ var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet"; @@ -4740,7 +4757,7 @@ function dbf_to_aoa(buf, opts) { case 0x83: memo = true; break; case 0x8B: memo = true; break; case 0xF5: memo = true; break; - default: process.exit(); throw new Error("DBF Unsupported Version: " + ft.toString(16)); + default: throw new Error("DBF Unsupported Version: " + ft.toString(16)); } var filedate = new Date(d.read_shift(1) + 1900, d.read_shift(1) - 1, d.read_shift(1)); var nrow = d.read_shift(4); @@ -8984,6 +9001,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { var ff = cell.F && cell.F.substr(0, ref.length) == ref ? {t:"array", ref:cell.F} : null; v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : ""); } + if(cell.l) ws['!links'].push([ref, cell.l]); return writextag('c', v, o); } @@ -9119,7 +9137,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { } }; })(); -function write_ws_xml_data(ws, opts, idx, wb) { +function write_ws_xml_data(ws, opts, idx, wb, rels) { var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R=0, C=0; for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C); for(R = range.s.r; R <= range.e.r; ++R) { @@ -9140,27 +9158,43 @@ var WS_XML_ROOT = writextag('worksheet', null, { 'xmlns:r': XMLNS.r }); -function write_ws_xml(idx, opts, wb) { +function write_ws_xml(idx, opts, wb, rels) { var o = [XML_HEADER, WS_XML_ROOT]; var s = wb.SheetNames[idx], sidx = 0, rdata = ""; var ws = wb.Sheets[s]; if(ws === undefined) ws = {}; var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; + if(!rels) rels = {}; + + o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); o[o.length] = (writextag('dimension', null, {'ref': ref})); if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); o[sidx = o.length] = ''; + ws['!links'] = []; if(ws['!ref'] != null) { - rdata = write_ws_xml_data(ws, opts, idx, wb); + rdata = write_ws_xml_data(ws, opts, idx, wb, rels); if(rdata.length > 0) o[o.length] = (rdata); } if(o.length>sidx+1) { o[o.length] = (''); o[sidx]=o[sidx].replace("/>",">"); } if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); - if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } - + var relc = -1, rel; + if(ws['!links'].length > 0) { + o[o.length] = ""; + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK); + rel = ({"ref":l[0], "r:id":"rId"+rId}); + if((relc = l[1].Target.indexOf("#")) > -1) rel.location = escapexml(l[1].Target.substr(relc+1)); + if(l[1].Tooltip) rel.tooltip = escapexml(l[1].Tooltip); + o[o.length] = writextag("hyperlink",null,rel); + }); + o[o.length] = ""; + } delete ws['!links']; + if(o.length>2) { o[o.length] = (''); o[1]=o[1].replace("/>",">"); } return o.join(""); } @@ -9223,6 +9257,14 @@ function parse_BrtWsProp(data, length) { z.name = parse_XLSBCodeName(data, length - 19); return z; } +function write_BrtWsProp(str, o) { + if(o == null) o = new_buf(80+4*str.length); + for(var i = 0; i < 11; ++i) o.write_shift(1,0); + o.write_shift(-4,-1); + o.write_shift(-4,-1); + write_XLSBCodeName(str, o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.303 BrtCellBlank */ function parse_BrtCellBlank(data, length) { @@ -9393,6 +9435,17 @@ function parse_BrtHLink(data, length, opts) { data.l = end; return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display}; } +function write_BrtHLink(l, rId, o) { + if(o == null) o = new_buf(50+4*l[1].Target.length); + write_UncheckedRfX({s:decode_cell(l[0]), e:decode_cell(l[0])}, o); + write_RelID("rId" + rId, o); + var locidx = l[1].Target.indexOf("#"); + var location = locidx == -1 ? "" : l[1].Target.substr(locidx+1); + write_XLWideString(location || "", o); + write_XLWideString(l[1].Tooltip || "", o); + write_XLWideString("", o); + return o.slice(0, o.l); +} /* [MS-XLSB] 2.4.6 BrtArrFmla */ function parse_BrtArrFmla(data, length, opts) { @@ -9698,7 +9751,7 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles) { } /* TODO: something useful -- this is a stub */ -function write_ws_bin_cell(ba, cell, R, C, opts) { +function write_ws_bin_cell(ba, cell, R, C, opts, ws) { if(cell.v === undefined) return ""; var vv = ""; var olddate = null; switch(cell.t) { @@ -9715,6 +9768,7 @@ function write_ws_bin_cell(ba, cell, R, C, opts) { var o = ({r:R, c:C}); /* TODO: cell style */ //o.s = get_cell_style(opts.cellXfs, cell, opts); + if(cell.l) ws['!links'].push([encode_cell(o), cell.l]); switch(cell.t) { case 's': case 'str': if(opts.bookSST) { @@ -9755,7 +9809,7 @@ function write_CELLTABLE(ba, ws, idx, opts, wb) { ref = cols[C] + rr; if(!ws[ref]) continue; /* write cell */ - write_ws_bin_cell(ba, ws[ref], R, C, opts); + write_ws_bin_cell(ba, ws[ref], R, C, opts, ws); } } write_record(ba, 'BrtEndSheetData'); @@ -9775,12 +9829,23 @@ function write_COLINFOS(ba, ws, idx, opts, wb) { write_record(ba, 'BrtEndColInfos'); } -function write_ws_bin(idx, opts, wb) { +function write_HLINKS(ba, ws, rels) { + /* *BrtHLink */ + ws['!links'].forEach(function(l) { + if(!l[1].Target) return; + var rId = add_rels(rels, -1, l[1].Target.replace(/#.*$/, ""), RELS.HLINK); + write_record(ba, "BrtHLink", write_BrtHLink(l, rId)); + }); + delete ws['!links']; +} + +function write_ws_bin(idx, opts, wb, rels) { var ba = buf_array(); var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; var r = safe_decode_range(ws['!ref'] || "A1"); + ws['!links'] = []; write_record(ba, "BrtBeginSheet"); - /* [BrtWsProp] */ + write_record(ba, "BrtWsProp", write_BrtWsProp(s)); write_record(ba, "BrtWsDim", write_BrtWsDim(r)); /* [WSVIEWS2] */ /* [WSFMTINFO] */ @@ -9798,7 +9863,7 @@ function write_ws_bin(idx, opts, wb) { /* [BrtPhoneticInfo] */ /* *CONDITIONALFORMATTING */ /* [DVALS] */ - /* *BrtHLink */ + write_HLINKS(ba, ws, rels); /* [BrtPrintOptions] */ /* [BrtMargins] */ /* [BrtPageSetup] */ @@ -10169,7 +10234,7 @@ function safe1904(wb) { function write_wb_xml(wb, opts) { var o = [XML_HEADER]; o[o.length] = WB_XML_ROOT; - o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb)})); + o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); o[o.length] = ""; for(var i = 0; i != wb.SheetNames.length; ++i) o[o.length] = (writextag('sheet',null,{name:escapexml(wb.SheetNames[i].substr(0,31)), sheetId:""+(i+1), "r:id":"rId"+(i+1)})); @@ -10203,10 +10268,11 @@ function parse_BrtWbProp(data, length) { return [dwThemeVersion, strName]; } function write_BrtWbProp(data, o) { - if(!o) o = new_buf(8); + if(!o) o = new_buf(68); o.write_shift(4, 0); o.write_shift(4, 0); - return o; + write_XLSBCodeName("ThisWorkbook", o); + return o.slice(0, o.l); } function parse_BrtFRTArchID$(data, length) { @@ -10226,12 +10292,12 @@ function parse_BrtName(data, length, opts) { var name = parse_XLNameWideString(data); var formula = parse_XLSBNameParsedFormula(data, 0, opts); var comment = parse_XLNullableWideString(data); - if(0 /* fProc */) { + //if(0 /* fProc */) { // unusedstring1: XLNullableWideString // description: XLNullableWideString // helpTopic: XLNullableWideString // unusedstring2: XLNullableWideString - } + //} data.l = end; return {Name:name, Ptg:formula, Comment:comment}; } @@ -10367,22 +10433,22 @@ function write_wb_bin(wb, opts) { write_record(ba, "BrtBeginBook"); write_record(ba, "BrtFileVersion", write_BrtFileVersion()); /* [[BrtFileSharingIso] BrtFileSharing] */ - if(0) write_record(ba, "BrtWbProp", write_BrtWbProp()); + write_record(ba, "BrtWbProp", write_BrtWbProp()); /* [ACABSPATH] */ /* [[BrtBookProtectionIso] BrtBookProtection] */ - if(0) write_BOOKVIEWS(ba, wb, opts); + /* write_BOOKVIEWS(ba, wb, opts); */ write_BUNDLESHS(ba, wb, opts); /* [FNGROUP] */ /* [EXTERNALS] */ /* *BrtName */ - if(0) write_record(ba, "BrtCalcProp", write_BrtCalcProp()); + /* write_record(ba, "BrtCalcProp", write_BrtCalcProp()); */ /* [BrtOleSize] */ /* *(BrtUserBookView *FRT) */ /* [PIVOTCACHEIDS] */ /* [BrtWbFactoid] */ /* [SMARTTAGTYPES] */ /* [BrtWebOpt] */ - if(0) write_record(ba, "BrtFileRecover", write_BrtFileRecover()); + /* write_record(ba, "BrtFileRecover", write_BrtFileRecover()); */ /* [WEBPUBITEMS] */ /* [CRERRS] */ /* FRTWORKBOOK */ @@ -10443,8 +10509,8 @@ function write_wb(wb, name, opts) { return (name.slice(-4)===".bin" ? write_wb_bin : write_wb_xml)(wb, opts); } -function write_ws(data, name, opts, wb) { - return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb); +function write_ws(data, name, opts, wb, rels) { + return (name.slice(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb, rels); } function write_sty(data, name, opts) { @@ -11401,7 +11467,7 @@ function safe_format_xf(p, opts, date1904) { if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; - if(p.t === 'e'); + if(p.t === 'e'){} else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); @@ -14249,17 +14315,6 @@ function parse_zip(zip, opts) { } return out; } -function add_rels(rels, rId, f, type, relobj) { - if(!relobj) relobj = {}; - if(!rels['!id']) rels['!id'] = {}; - relobj.Id = 'rId' + rId; - relobj.Type = type; - relobj.Target = f; - if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId); - rels['!id'][relobj.Id] = relobj; - rels[('/' + relobj.Target).replace("//","/")] = relobj; -} - function write_zip(wb, opts) { if(opts.bookType == "ods") return write_ods(wb, opts); if(wb && !wb.SSF) { @@ -14313,9 +14368,11 @@ f = "docProps/app.xml"; for(rId=1;rId <= wb.SheetNames.length; ++rId) { f = "xl/worksheets/sheet" + rId + "." + wbext; - zip.file(f, write_ws(rId-1, f, opts, wb)); + var wsrels = {'!id':{}}; + zip.file(f, write_ws(rId-1, f, opts, wb, wsrels)); ct.sheets.push(f); add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]); + if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels)); // get_rels_path('') } if(opts.Strings != null && opts.Strings.length > 0) { @@ -14347,8 +14404,8 @@ f = "docProps/app.xml"; } zip.file("[Content_Types].xml", write_ct(ct, opts)); - zip.file('_rels/.rels', write_rels(opts.rels)); - zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); + zip.file('_rels/.rels', write_rels(opts.rels)); // get_rels_path('') + zip.file('xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); // get_rels_path("xl/workbook." + wbext) return zip; } function firstbyte(f,o) {