From 35e485756f1d3f5eb1e5f78a5cee06b5ed902645 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 21 Jul 2019 12:56:36 +0800 Subject: [PATCH] Resolve #217, new function add VBA project supported. --- excelize.go | 77 ++++++++++++++++++++++++++++++++++++++++++++ excelize_test.go | 11 +++++++ test/vbaProject.bin | Bin 0 -> 16896 bytes xmlDrawing.go | 1 + 4 files changed, 89 insertions(+) create mode 100755 test/vbaProject.bin diff --git a/excelize.go b/excelize.go index f636a84..c7eff10 100644 --- a/excelize.go +++ b/excelize.go @@ -19,7 +19,9 @@ import ( "io" "io/ioutil" "os" + "path" "strconv" + "strings" ) // File define a populated XLSX file struct. @@ -226,3 +228,78 @@ func (f *File) UpdateLinkedValue() error { } return nil } + +// AddVBAProject provides the method to add vbaProject.bin file which contains +// functions and/or macros. The file extension should be .xlsm. For example: +// +// err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")) +// if err != nil { +// fmt.Println(err) +// } +// err = f.AddVBAProject("vbaProject.bin") +// if err != nil { +// fmt.Println(err) +// } +// err = f.SaveAs("macros.xlsm") +// if err != nil { +// fmt.Println(err) +// } +// +func (f *File) AddVBAProject(bin string) error { + var err error + // Check vbaProject.bin exists first. + if _, err = os.Stat(bin); os.IsNotExist(err) { + return err + } + if path.Ext(bin) != ".bin" { + return errors.New("unsupported VBA project extension") + } + f.setContentTypePartVBAProjectExtensions() + wb := f.workbookRelsReader() + var rID int + var ok bool + for _, rel := range wb.Relationships { + if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject { + ok = true + continue + } + t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) + if t > rID { + rID = t + } + } + rID++ + if !ok { + wb.Relationships = append(wb.Relationships, xlsxWorkbookRelation{ + ID: "rId" + strconv.Itoa(rID), + Target: "vbaProject.bin", + Type: SourceRelationshipVBAProject, + }) + } + file, _ := ioutil.ReadFile(bin) + f.XLSX["xl/vbaProject.bin"] = file + return err +} + +// setContentTypePartVBAProjectExtensions provides a function to set the +// content type for relationship parts and the main document part. +func (f *File) setContentTypePartVBAProjectExtensions() { + var ok bool + content := f.contentTypesReader() + for _, v := range content.Defaults { + if v.Extension == "bin" { + ok = true + } + } + for idx, o := range content.Overrides { + if o.PartName == "/xl/workbook.xml" { + content.Overrides[idx].ContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" + } + } + if !ok { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "bin", + ContentType: "application/vnd.ms-office.vbaProject", + }) + } +} diff --git a/excelize_test.go b/excelize_test.go index c4a06a5..79010b1 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -1078,6 +1078,17 @@ func TestSetDefaultTimeStyle(t *testing.T) { assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN is not exist") } +func TestAddVBAProject(t *testing.T) { + f := NewFile() + assert.NoError(t, f.SetSheetPrOptions("Sheet1", CodeName("Sheet1"))) + assert.EqualError(t, f.AddVBAProject("macros.bin"), "stat macros.bin: no such file or directory") + assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "Book1.xlsx")), "unsupported VBA project extension") + assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) + // Test add VBA project twice. + assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) +} + func prepareTestBook1() (*File, error) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if err != nil { diff --git a/test/vbaProject.bin b/test/vbaProject.bin new file mode 100755 index 0000000000000000000000000000000000000000..fc15dca28e5ffd30e75f7464d4306110caaf3ff9 GIT binary patch literal 16896 zcmeHO3vgW3c|P~gW*C!vi=X#$0$HUtW-A;KlJG$fdWPSdngOrRaybfz%VnUb{C_WSO+ zSGv2bWXWWJv>fTX=iLAN=Y9VF{^zl0URiMV!IMkg5IJtGu!!+Yp(rrX6@&@Rl?tIE zK#XTH8A%iZ!B5<8Q4jnN>N*dDw+OHTa{!L{T)+kt0!2VEPzIC$^MQ*1OaZX~xEQz$ zSO{DKa6ViLzZ_T$ECDKjrNA=aa)3Oe%jb^=@n%8%kNCG-xFW8*8r^ZTKLxj%>d_~5AXv4 zAP9s2@~wxzK@Wc#ek-sExDIFo+JVmin}H5s3vfNK71#!B2W|kE*ADodz-NKa0bM{C zhydL{56}xlfj;0yU?H{IJgt+(Z^9tT49F~LMrB{u--{=TFpB!H{c6-r6&mdoF!vl7ONaL7!m|Mq&K>g&2LGKfHvy>iP= zd`-5+69e7xcLI2<1D-hCx| z*2Uu8k(hF(v|~8ZqZ|oEV%B7|xYwVEMpCv&cWlw+?TKjb;b>1RA__M2ij%f2iIEat zaCA5h#)V6_LSEo#{j-$*hQAM7u<)f%u+N9J>s{{nsmdTm0y1&#*D z{kIW*68HhXEO6|L9q&e+Es@1 zCvzXI|LMOyJ?}q4_jLWzzM%b$%`?;oxyf>ima}sv`e5P2{K{lL8r{&2ZcxRJY4m|A z8lWSpwccD?DW@WJM7h@O3@x1q$=K=i2DQheW-y0lqBjgBttiK#vuCR}mcf>w{!kA{ zePctt!+1BOd@>|Qa2eJbM-6_q+@t0{&@>lY5a&ozj=r4%xOofe z)MHx65~jyEI)>@q!`Q~g2gFX4&H8lW-KyDT52!5HT#tizr#lQkw;quL4z0e_H}?zd5v{Z2VPqsWHep)}A`*%3F>Pk@i~Gk`H( zxjt)g0`cZ_PwVMG*4o6j8IJtyfBG|V$1s=j@%;?+zqZP_cc$RUmu@QB81gs)nA2K`ED!&7&>t|-e>!;WT=w%Y=!Z;n4gu*0LEn>y&icf1=&ClFy6_{tS+1EM z)6h}oWoS}&8Sgt0*Fa`C=F}|Mv6usDr>1KRzDeH(jx%7D_Lx$Jg^Wa-zpHV#wxB4YzgC(F4|?8ddii*64EO1;ZM(#SCk-G-X($_FlspT_7GctWjIsutr5pw?+$Y zRl(OAD`jkc%D(@X85W2D|tV#2;9 zlDILNvWu3aeRE{!#;B;V`y)d=(HM+#UpyX*Muu!FONuPttMf-=B9^Rn*4W)E4~YJf zBIio`YI{jhr7L||aHv-al@wV^ijHpTh{mElW2yf5kbmdCmZPnOqO7Fot}+hF>|Emd zw4Q*z5xxvz-FDV}i?&(^8PkgsY?4>6F{pd2cHy{ND9<%0 zA#|e|i?%N*Peo`6Szwh|#3{7k?6?YxKow&^GkCc_Dkj=0^XE6P_&oV*>6jJ{>nhaC zqxlMad`9k&G5MeQ^$$M^hF&oSlOA|84^O%OC@nqL$H3An zmX=;tv)=zQ3;Dkl=NF&I|8Gobd?NqZUCFtbO_6V;^LuB+@!bgL`XAQ$!};Xj{aYjd zFGoJl?#)>7Z7sxx!owELwqPfNY{eIrA7J;o3N}z37W`GP_^NRB zdlgD($2n>o=dKBy$qouT^6$rqYZND|cGz#D*dp-aG`bVqal{YeIh4iKiL+rWTD4q~ zi0#vERnWeVfELB+ZYNHLdE3G8y?Q8!oa=D<+={;{@bEsu2vT9?3;qb+m@jL@I|^6^ zV=Xz1SW+V>rC+N{QtLD0u{2Mw@vP9D(`#LNox0He%{bNGh@SOpC)q|$)BCvpkR=V% zbIaIW@Z!8s0N0D!^Zfck9k*-DQaF=*xF)H~v;K6Uw$Ar*C6IEa$-MQ?WUhX3BMa9`ao$x$O)Pg zZ?!-G`PS?0Y(Xqr>A|0*yKp~%8?|+ym|OB;Xv#L!HiX+Z9Dg|*HlfGbP!~f_=9>xT zIdma&f!`bXhf_Cbhyqm+JiRW0U#jnN8$B*}wavO`y0FEhrzY(Oxz}gpX!twcwc_+| zs+(yGF-ESe9rAk2pG;%hL?9VN4-~%r7jOX@NwFQ*Zsf&;7$NvV?oxtfrG$3 zz!!io0*8Pv0lx?QK5!WLGH?Vq3fv2P1-K8mAHX3*=Bx0(27DcO0Qdvo4}ot0#{u$u z6aF6o+^hLx;9J1Afro)dfIk5q1s(&w15kJUDR2Tf2|Nxw0ela55_k$=zE8vF9tOjI zq5IFme-8LQFa|sioCf|9cmW{q|0f4kBQGCvTD||^C6igKYV%}*FO%Ut5V~?sr5|FO zNuNqOiwpMv9k4UZXE<_(R%B)6LOO2g&^efuX;$i4xwWi3d9qrTm6aP=adk+yeljbL zqoQ!9`QtF6Y~xh*U{{*spe(Z9&2b<1FbCk{R>z@D4<%PG{nm9)EWG8>=YvBFSw97n z<;B9tH`e{ji*3(ccl)LH{c?MyLI24!_s#!`?Z>`T+n#y(-0q*8H0bx1J^S@@yEgfr ztU7-8{ZDOq(V){WM}BDl_uQ3bA|pIlV9zNIj^daqy*ex`#an$X|M`osm}A}6Kjid2 zf4d%ZyXM}3XzQj(&l8V_h1Ci^?{}&g`^-a6yzCslJS+-|lc`>ua~%zbMG-SSrMI&KRT#Wl?#`Uv-J#U6ZVH%@hQAAhp=16P==W*qK&m`C;!g=7^ zOD4jubCY5Bzanh2wkP_BQXQ$p!1Kt)%451DQ$0V9I%v6OGF`r|nkH%ujC0|0#bwXDl z+7}s#rFNYT3w-(&B_?ojM77{24;=1nhgATm>I|m-d@$kERx>8bkQ+bTJjRjvEk-`` zMFn`cBV7h4Vm+R@j*~arT^L0us~%5toy~$At5o%Hp%`9Ru-+o}tD^T|ixofqSSpMO zcd#cl2ezPSRu}FnP>Y(>qPc29S!kP6P*$jxX$H}~qFCK{jaqbxP!ef5Y1gWYCrsNk zZQBy8iC3tf6P8t->u@VFjcbt}H;$og%dA#EY_0i987@T%@7^j`oudk?bpKVIkQtcH zwMMeBWv^Ym_hJ1@D+ zhu- zcwVbm<@!;D>!k`l#UpR7Ikoq-3ippH+%HvZr1w_Et$SXoC{=vdXHPl49sTsG&%-uG zNUnVq&CX>EC#0N|q#2<(IAY27hg&=Zyu(KxAK~D<#S^k|1CAcx|DMFZ(Y6xKnExuBQ02%V5 ziw|WgnlvQH5TM>pXd1$cN6=drXl+H;KuKz4qUa)dR4oUAZRhacq<3GTtOtxwvtt!6 z<{A!eOnZvZMac9jgr}&YF4w4){IYgR4?-Q>?hm%s1-A#;G3;aQ^UjLhq!-TF!5h_d zU)5=g_0enAsK$d8bL8|Lrl)#27<7ea=sQi_NoDG)gT_OH?_dpBj3l!P_vppQ2`Y;@ zsnfW%O4lwu{mCk{o_VkK*E=dopZ#a7vjYFLnZp-E{1km7wck|3C)J;!{aRa%O3Dso zC3ou;k(HD$59+i=eb$EGSIM8{3?M6>d)J$^+^goK<=zn|Z9zZ|&6q4HgkDlPl_rcF z$hul#Cdk`KW&?!zgmUV|WQ((s<(MNVsH7Md0-C`j-J;wRaU)`8zC8a=gV_Kkho&)a z(=$e|>pJZHdXA$YH7E@c=}^T&L*E$pillbs-RQ}UeuQ2X8+b7yAz-+zuGcIw4smp{9*$<6(D z>VJOKSPRn5=T3p4L@I#uvCb%dvh_17e@2xjt<&ib)iwk@?plYV#o=#sIvtMsmc3WwlYjL@Du