From 90bdc7c12e709f5603e4003a07605aa4b8143cce Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 14 Jun 2022 08:33:36 -0700 Subject: [PATCH 1/2] Test For Excel File Saved With Ribbon Data (#2883) File from https://www.rondebruin.nl/win/s2/win003.htm. I have been in conversation with the author, who has no objection to its use. I have not actually opened the file in Excel (at least not with macros enabled); I am using it merely to demonstrate that the ribbon data is read and written correctly. Test added; no source code changed. This should slightly increase coverage for Reader/Xlsx (moderate), Writer/Xlsx (slight), and Spreadsheet (substantial). Note that this file has no Ribbon Bin objects, so some coverage is still lacking. --- .../Reader/Xlsx/RibbonTest.php | 47 ++++++++++++++++++ tests/data/Reader/XLSX/ribbon.donotopen.zip | Bin 0 -> 15340 bytes 2 files changed, 47 insertions(+) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php create mode 100644 tests/data/Reader/XLSX/ribbon.donotopen.zip diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php new file mode 100644 index 00000000..197ad47f --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php @@ -0,0 +1,47 @@ +load($filename); + self::assertTrue($spreadsheet->hasRibbon()); + $target = $spreadsheet->getRibbonXMLData('target'); + self::assertSame('customUI/customUI.xml', $target); + $data = $spreadsheet->getRibbonXMLData('data'); + self::assertIsString($data); + self::assertSame(1522, strlen($data)); + $vbaCode = (string) $spreadsheet->getMacrosCode(); + self::assertSame(13312, strlen($vbaCode)); + self::assertNull($spreadsheet->getRibbonBinObjects()); + self::assertNull($spreadsheet->getRibbonBinObjects('names')); + self::assertNull($spreadsheet->getRibbonBinObjects('data')); + self::assertEmpty($spreadsheet->getRibbonBinObjects('types')); + self::assertNull($spreadsheet->getRibbonBinObjects('xxxxx')); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertTrue($reloadedSpreadsheet->hasRibbon()); + $ribbonData = $reloadedSpreadsheet->getRibbonXmlData(); + self::assertIsArray($ribbonData); + self::assertSame($target, $ribbonData['target'] ?? ''); + self::assertSame($data, $ribbonData['data'] ?? ''); + self::assertSame($vbaCode, $reloadedSpreadsheet->getMacrosCode()); + self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/ribbon.donotopen.zip b/tests/data/Reader/XLSX/ribbon.donotopen.zip new file mode 100644 index 0000000000000000000000000000000000000000..97d58bdcf63e278ecdd03bdcd454e39fc95f14e6 GIT binary patch literal 15340 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 literal 0 HcmV?d00001 From 04f46676584abcc144a441aa516a8e2a2035eb1b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 14 Jun 2022 08:45:12 -0700 Subject: [PATCH 2/2] Expand Chart Support for schemeClr and prstClr (#2879) * Expand Chart Support for schemeClr and prstClr Fix #2219. Address some, but not all, issues mentioned in #2863. For Pie Charts, fill color is stored in XML as part of dPt node, which had been ignored by Reader/Xlsx/Chart. Add support for it, including when specified as schemeClr or prstClr rather than srgbClr. Add support for prstClr in other cases where schemeClr is supported. * Update Change Log Add this PR. --- CHANGELOG.md | 2 +- samples/templates/32readwriteAreaChart4.xlsx | Bin 0 -> 12474 bytes src/PhpSpreadsheet/Chart/DataSeriesValues.php | 25 ++- src/PhpSpreadsheet/Chart/Properties.php | 5 + src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 90 ++++++---- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 34 +++- .../Chart/Charts32XmlTest.php | 95 ++++++----- .../PhpSpreadsheetTests/Chart/PieFillTest.php | 160 ++++++++++++++++++ 8 files changed, 325 insertions(+), 86 deletions(-) create mode 100644 samples/templates/32readwriteAreaChart4.xlsx create mode 100644 tests/PhpSpreadsheetTests/Chart/PieFillTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a12675..4664f049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Time interval formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772) - Copy from Xls(x) to Html/Pdf loses drawings [PR #2788](https://github.com/PHPOffice/PhpSpreadsheet/pull/2788) - Html Reader converting cell containing 0 to null string [Issue #2810](https://github.com/PHPOffice/PhpSpreadsheet/issues/2810) [PR #2813](https://github.com/PHPOffice/PhpSpreadsheet/pull/2813) -- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) +- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [Issue #2219](https://github.com/PHPOffice/PhpSpreadsheet/issues/2219) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) [PR #2856](https://github.com/PHPOffice/PhpSpreadsheet/pull/2856) [PR #2865](https://github.com/PHPOffice/PhpSpreadsheet/pull/2865) [PR #2872](https://github.com/PHPOffice/PhpSpreadsheet/pull/2872) [PR #2879](https://github.com/PHPOffice/PhpSpreadsheet/pull/2879) ## 1.23.0 - 2022-04-24 diff --git a/samples/templates/32readwriteAreaChart4.xlsx b/samples/templates/32readwriteAreaChart4.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4548bf2e0dec273a37ceaf11b27e1a914c8978c5 GIT binary patch literal 12474 zcmeHt^J5;{_IIqtwr$(C+o);t#I|kQb{gAm8ryD+ri~k``9AHr_x7Be^S=MUz4OD& zvoo_lnZ0JMz4TF*1BXBdfdYX60RbTasqCeO%K!xdafJc_K?8vS(-E_=b~Lhfe6Q|i zYviEI=xSw2oDTs;oeKg6eExsOf3XBAl13GKnUN)Kq#wn08O+~m5(lO`tLVW>e}u;N zSkZK{9A&#azmflpNurMXdYn>m`!?I-DK(PvZ~#M2+BBfL&xr>e(v=|c>O$7DLkQtS z+^?`hh0(R74@y~nAO)-7MgfA}wP8?pPW=3x>>LL>+$eI7!z6v7B#O|7)<{{VIsDym zTUR=#Gl*=cIrGr-15o2{qblbu`-?hMISUc`ZBJDauSNS0D&F)-?#*Nf#fqekS3sd% zYV^)f3_y(NAqYp&8Pv(k+X(mdC=UgUK_DsxoTw)8v*PuxNOo)=JR`4UKdT)Nbe`Fh zTjaXF4$FSsyS%qD{8~C<5*c&PE>5kOQb}c*`tYVJ=ImmS+QDJ1K92J}=E<2z$Dr{3 z;T>k3JT$V|4v2?hJH|2TBCVer*?Q;2I>O_^$r&JCyAgBuWdG4^1J=ow!-G7r^=s7d z9`P&Z+N9ap^cacBSJgLdgST%7-l>J+3y^cKee9xdEP##+B905n`Q&)u`;6Q=mW}wR z=9yQy12qQPH1-^I&IMG*=Vx#b<-gc;<7;NJE8rb@pvk@h+Vp#SBTEM+#-Hc^+46s} z0sm>~k@1s2TZSJvmF^JRefVZA4U<>YE-&3qqE7gc{Di16s(@Vh<~k7EU)ErC`ufYH zkk0zazL`1^zCM#;ZyU8pEt|f_qPc5j&Ksm$eNq!M^g-Nce1OKy#fOr* zhzQ)a#78oHb^;~zSLa9eMRCk3BLLIs1e_A1&xv~i!QF()4E*yw<}lD6&}D-6{C9QD z>(I|v^h`Z4C1mfm(w@NX9fZYdbv0($z9)ZhQ3mBd3Q9zg&-pAa|*2rdW= zsH-K@ABN&$V{f5vV`K5t_x;;wK!FYr`0Ri8Q4v3C-p33taTD|-V7kkW)k`_p&Ujii z)hff=*EW$`ULShYYxmSbm3R1KtaTmBTi6W`@`hC(m&#l6$*6j`I&w&nH|=$`js7>= zd;2I5&6j*w=Sg^CwB#@aJe5A52hm>FJnBE_G>OEha=|jocxMC3YnX zd?Q;R=Q6t|e)X?NvVZed=R(b9tHMZ2c?|!Lluhs6F-yw=eb7B0Box zH*!}04*YH{Ho?BesYF3po~~t7u_nsilnW_Nalkfrl4f+4MCtX2qh7KSWPFVKKHEfVkvpbq_6dm>d7Y(6o= zcPg#;gxV9CfjfdvlPXx^rkFtsPH64SgtC{$I|lg(HNOT7X5p_+GI>vyUlq;lLT73$ z6tZWFC;ARos_!SxJe@p!4MHUfj1$!Xr|I-BK0G=H%xk;+Xu6h<{dk+0Ofn}o;Q3CJ z;<0Z*uSQK%Bh0sykL5id@=OXy-b|#F$|u@9cmnL?6CwO2YIQ`s3@(gR=2-42(#`uW z^~U1mGG$!E08-L#_iE}bHxg?3K}a^Lkg+sb2Ie{~SjLGhAH1y+2Tt^H?MCV{hM1(t z(etMPA>SpPFC!+om3R$J`b9(zPsMa)8KsO0t@x<5JqYS0L}gZ8qGf|wi#$iZG_OFH zsNvWe;|kpz9U*zy5PQ!`>AO4Op+Kyc);mWW4we{|o^3U;o-`TkC^I@n>$gpTn_L7U zg`Q_GGyS9353(Ys7Jzr`fucwML+lQw0DB`tHAj0hYZHf`mTNgRDGR{@4|UeQ$(Ojk zHc_=L#H;%TFhO?!p2#_&zT(&6%&)kzU?(}C3(Oy?t1yoZBZUYKTsZs9w{v5NxLT}tU>PB$paVGqV(d%V#SQ${@j z1(3}A6{odISQ>vB(Bn_&I>;l)Z^Nx`4L;Zo-HQL@)0F(%$J0cI5;~Wie?d^I)3z$zNAk1{ocqwM>#w!jd82M zMNj=+{7>6+Ff}rA{P*&I4Sp}aB5vFq==FS01G)mHJDq2Gu?rNL45kiO1bj{C!n8>7 zPM+CYPL)k4`dT?GWIiG*vp;jCo?F(-y< z4&S(}Vr6E|kq{afzozNKl#F{cg?~UX8RRu8T=}Y&mNnM^Ps#Uu0>z&3p_OZo3%m_d z?KmK)4hc_r(4i{h*|@kXhA=1oIW=x~01hpjZ+?_^CJD6Pe<20;z5g2xBj#qSmFI?G zig-PqTzB}Y>uD`Gh5P4fwep4Q0jzKg5P6YOUu?_kIKzJ;VsOEh?DwwK5y)hIbE#;gyxBnlJD8A zmg`ozhxQ2)&2pQ1xCB4R;St8e4H}HO%CD?vN}sMQJe=zGXV8+Ph|~xsXS5s#$x>t_ zg0ZvL@jWMLj|T^@_2 zQQr!nyYV>V^ws~|ar_LuhuTE@a8XCY+5R5t;~Z<%;-QmAV`es}-UhTnCA~!Y)ep(a zglT%;qaD^`>qeEz_FJ&0D~dFoEXCq!S)*EUgdxF&lry|}V=s^K9=zCouH>_;3Fqcj z$1^Cy^KlB#XW4Qsmvbm|R$Qf&sg=AZZ&`s5dG1w)AR7?Rg7i;V)Sbi`glDyXkMF!#iwymGJ3 z_Idtxr*`@6E@3BRGMeazxo8NVQ-iz3i|~QunTg-Z9>2( zi(o5uL96M?9T8x^um7fxK+jWipgqE1u(4AM>^0jf*}?0zSZ@6jHhen#*i`DmFSm)Y zsK+NN-7m3pGqHapo>UtN5B~nhHGCfKvv&Fso-JQQLio`) zx+SA^4>Tqtl#Vl0Wxlj%dPYjBh-sBo3yuX`!tZu7*Oa#E!GqMG)pBd{`j{WR5~>3c z9@G++4evf>RLZk(!J6AX0FJu~Yt*1oN#+rg^AHf&l7c}ZIH>5JwZX^SRp1iU3+iQR zyFh!+s%1c(9d+d>(YDL;>tB(rIA3!x6nTCK#SYvdW7lW1^X+-!RTs%&NwVDm(E5A( z9r9RNvF%}gVgDMQq7P*<$Ra12nXHqQuSKcTLL=P@zh6mAc<_d{UFGDlEH$y4_;ono zQAkbVl*$&eR&asvY|B|wuaK?i*e*{L@ijGU=?2ymcj^l0G?+#QGlB@D%iMl9amao5 zIFDR!Ax9?pJSakJZVgeXGIAY~(XjXiO`rMKbt9R&#MrPB{}$F-{g@#VfS4|LhP%y; zGEAOznT$3R{E6XX>!}%36xiA>`o(yu;oZ80!)DHjrzLBEsx<*8f`ak$+rAC~&7(5s zPOa?YP6>(2zWnhr6D`m&I2{o>g@;A-txTQDN=-T3Y7W+|j}fD|y?X1&6t;1Jmqbg? zC1+_*n|WMwBtpI`;hPUZlu!V06|Z4VLTM~%K5qYkVZ;@^nZgRQ*##LLhSdtDmf7*U zZ04rqBl;hL#kCjvV@6+gwm2;Pj3<^BnME_(5lE4Y~>KTWH-7aqO5p2 z`BcqOgryTU3mWHnVdR|){ZmWJN&+)P%|z(&op4fR)#CBKkh?O!>KzqErmU6Hj5uN- z_CE+|OE;8N@2dmf8EYcIlqA*JNK`em-;6a!8<%nZbb`pV2|3!pT2`ax`$}DQ14EGnZRRZn!Q=%;HJ|+*z$k_ z^O2h<=@7#VzCovKkI-6$X`>lqjshU+>1}%@b*P%ana&`ID1wD7tI8V^#*cE$eO1Q3 z-%z6*ZM--AR9Uv^p}ZjaFvD$9s2*>N9FQQzB+P@e_c)<&+`NtBtVXOA(OJXw5p%5D z;D%h3r1RkXv1co0n*SQ+8cg+vhvpnQ_>a>LvZ2$fLMR*YvhZeW;s=!n8%@GHGLs_o zx!z(H`D&s3f|(=OPi?i6o7wkfR^i!<#HMpWCIZ+?;?}BYkIPmFh)fkG5CrA}3<=;u zCO<&gY$ga0)`Yd1LEGLTspQu3Fi4LdKiQ^j)<1^rS74U+e0$}g-V`8xrq$&XfTi1p z-B8;Ml}+-fQmN(d^VUYN!g=vw*$~G%IpGdcSN=`JxOH8s4Ojb`H^#)L2!`w*wO zN`=%~(=JZ@w>X64+QvRzduN%5B7jW#l5OOPto?F{`mJlw7_(J3<28k->9 zA$v$lxh_7h?uO5iIPc`QV`R`98j16YXC>lR#L03U8A%>;gK|O4mre`p?w_W)@UPbl zn5o}sw;q(yieJX)&CRt9?Ij7jl^0J*G^rz{xlNN9>gK0#)2c5*31T$rzmfCC3S);3 zAKc9x(oithF=N*xVpFLZWUF}Sx2P}KU+{syXAgr@2@kLY%~GQjxdH3LXj8>7Oh|N_CKW$eqtH||@=V?5veyEY@G7#%SvitE zei$w1`-Ph!6Zle(k0X3bAjkyAr(-7qt30lw$xq>Te!3IiBV6B|@4Ch#H?5hA!Kzb0 zogYS~pjd(iTAQs%XZnGASF)?9`oq?MKRR5@K#IgSq*-q_I(O+HCgJd8fkd=6X_%xEVe)K>}n;CT3f>9JBR? zr!xi{u6@%@hv4z{mufx+Q*c()0HP4^ESL0q5%I3MprFm|7+@-*-^P0ERhVlsxv1iJ z>f#+pzRkXOJ{IZ?=Ha9_4=CegzRrMrztC(kfWcW&3sA$Om7KI(RkVi}$T)7l> zvjqNiZ~^$^=;x31OV`PQ+E!cnX)X;OF1lT=a(*+h2>i=8Ihs8N?Yy;AR!;glD6^Za z(;;UeOgA|u33rP&7k6KfT=E`5h}wvJZrnMz zP*d(fD4T{pD=E71v$wtmr9CI_^U^D8m=(#T0o}c^!BiFqhb>_|$>#yxby=nEfrK=2 zoR0VX4&PcyO-&Txs2~gpS~U>t``ur#Y#`Yej=w{f4{oHt_8KCQXuZ$ewEe=Tp)xaL z<$4Ndgzlaj&sl5AiB{#dd-msbQK8YygIk}1X`)lW8q|FD^bAl=?0reP|w4(YuK^Z zWu>2zUq`FM{S%zf~$B<&HR{g8! z%JM?Hl1UfeaI7Gs4&YpMqrrD@%ahGTLXg60J-h9AZGMl};Td&1k9~aA^Uy>Q4zW+` zp3hO|pfco7fx3+l?`nv3s98bz041g6IV50u(@+%`9*OZHyWk&8IJ0}wDOHxSqAXA?&QN$mp2av!*{QSAk zYMj@(hM;M0kP9rRA zElu%61EE12)N6_Ck%N+T3obMz9=^1K%ItCWSW5x-t{x1_ff4&-3)TDmFKlHsdb#Ek zXXBd(OedPEN@Qvl<>YG1DtxZNYPhn ztUdb{9!>+RmUvNf64Mw$Us_)ld%<2iW;YRi5~j$QbYYYSgE4ELMTH>jYaJka<+W>& z{S>=YB0c1JR08&uoxK|$m1({o058W&B(UvK{%fbsE#+rw=dIvwf)mB2X;BgM$9*`P zBqYiruz^Y8vxZ<+wLpC;dP{WkHeqx*1(jrLV*P#!)PrLrDXQf**a`U`PL7PeoJMEX zWn8xX@Vw3)oUh-Jz9G9W%d%l|-AaeTvasYzT(jz{Ebpu5#vL@{MNBZorWj@(|4h2* zZequZ8$!0!Hh^-LP>f{BMYxK1y|!%&^6^NoI-%+wdr^4_(Qq9h75i%keht`ofKol} z(xHhuop%wH5_3Ns7PKVo$h^GXT#V{qtEhfdlCC25QKR>n9I~-V0e2vBz9o8)3Tm|2 z#~}s_VZrT-e>kKft;XF1~+6GyTWjX zXb2Pxdw<-zC0MM8bR1wX`CdhnEGKShoM%szaR1e-Cz7C7vq60leiC#`Ry9+iHs`W2 zZ)T~a^E_@$ZqSaWN9?}9OduqXafF~vfD*NJ&>+E^B@GmOrPn~lq7SL{*5GVYRG89q zw3=35k|}@lsiClu($?;Cf-XU@3U5o%;<#Oi4u(12ow0(hsDkE7iN&?z+et-TH~&Jp zGQb^GzcriaRml7vN@2_?)t4J4{3^x5{&~VH2kN=64)A-H=Z7W5OH$1NNKdYzo>Zv3 zai-{3BjSpb#~N&w^avgj`c$YN0M%n9=affWN!)3Gxl_B9CEV)=HtV;F9ujqu3fBlA z(3O=U-O8^nZkM#D_Bzs*C~aS(HALxjXsep7==a*Z+v32dXR>Ri;L5xS&7Rtq?v$hu z%wwu)N%9VpK)k6&yWeqdEp!#>I%0yoek-<$RTVT~hFN5X9?-`kKuA&59?jW^U%Gs) zV{RBtZRzg3O1hcXnxHcrU-PPg!yF0Xl|xwzlvzjXuC>iu^Kg+Ttix zLXMKXI_q6Hhm-x>4~4mCHk-AniONo%Z`THpphgnUy{85@*}= z!R{pNwvY=45jZcWglD@d`6O>Hjo>Tna=b_#N4NvUfau;kq|cRYItc{bRNoN_+QBPc zk(LkI7Ih`Feffs_TDM$in&Anm$}6_Lpv-io5jC(B@(moSd7DoF%4dpfJ4B(KBay%b zY+qe8%Fd!rOoZzk^dEdRi$y&>3F2K;?gwR3F&tHCy}c4*nhjt%_o|GDy0uM0dr^h^kX^T$COx?O9!GS}Vh--eUE%omtDghkwsg!CaL<(8CM=vG~4 zymYG74#i^L_3ZAt580*NBI2OgVO!R+3}JSZ{Y+vy%ns9lq2$H#kRvrQIEQ;)<0Jkd zJ=|w!O!-Z}4WIBNTy{~hU?#b!TgAm_xKOlzaaFdY)T6^ytpqN7Vw2Ufrro_?^%glR z_1-~RsxV7IoVlW`4NUFuXMgBx|8J2<fR|{!DnCcfgXM zRI55FQ3@ByI4^iYd)4yfekFs^him~uqhAy){cWMk$7F%WDRr3)0~MuImBFETCjD)M zM{dxNN+RW+Kuv>NvD&O_$jxx)5ldYc<7f;nQe^GGeVGS>y4rTxmuG~^1iae=Gmj&P zrSwT%tPb#GwUyKLJ@L}5?a>|pvpXYh-fFJLaCS@+yZd^2RDiDwswEywpkxm8J}5wQ z1=o|(!u{RV0r#H&qm9;TBds^+makeVVO_|OKgZGs;|ejU%;U+#oKguxR&DrMTlz2S zZpSu9_UzJjX82%~$S8~`&Dr2HF~$gK&mtX}Xfg0;r-2Sg@HPhn;G;f2WPj}Mun9P? zk`ry2Ii!fyn~@kWj0%4J;^!7yw8tFyxe?h#Gl$z_^6zbh?iE`^vYP-#B{Ic`*cB;B^a#-O8-y_%7u8wq&5Sf>J~3_l0Ao;4Q4&Oir$14icW}<8P@jWwT??#1 zt!8QKsMlDl!|7Ny;6pI(#MG`!ly1eDPCw#8jeK{Q)!|}|M7JU^(y_J+;>B(8>oWwu zK-~2|(bZ$Nu7!azC^2TeoY20N8gMNOf}GQOAMdJOnv$fQ+xn#sy1hH<^Q<_EM8idv zzi08(!~-*N1blfG^~gW~C=nBp$MG;#fxuaNM>Qn^=ci$caC3CI?b_`EWY>49fqglx zwUZyRstLDC2G!_9cg+qp5E1r2Tz`vm&El$W^7k1b4@O{Qfm2=;B8-z7oQBstQo z1TH-Cnq|lu$!4zU2Z(WM7nVJf)e3xLf1Q7E`!PbMOyQA~EPE!bd`h6Aqs>DT*HoYW z;cMHEz2$roZD)6PvD;N z_mAi2u3-POr~V#z7v;}8j)9H6(f_s_pgRBS@QhcH>t#j{+JJl(8}`U3#P}k*+LI{2 zgPcj-6|`$jXE7Tq-u~n<>$rf}Kls_<#AULR{5^gK?>&S;`5TN-N(hNuaXPw2x~WlP zjE%e@-YW8H9Y{!CKZ%I%_q15)VGAD<%s8+${G#|}v?v|@t|!cbYcV`bTNFCP-0W7< zgZZA>`6^nu&j>aXV|70J?!@y&d+1e2k@AuiVaMI~&Vdp`2IJx?Pw1yBKkhi`;Xas% zDN{~-u%|k9;T_DXE5h!Z_G*|nJ!)h8T8G^GrTEtB=@$P@6y5xWEC{UwO)B$8U5gP8 zt`O5=PD0v7$IU2NkK%sPd;?j%TK+=K_iLEs0r*J z3=>qH*$f$etT zte4*a-9jY*UE(JL24kf+ld6t~2&$LeuXXP_&Y>vsh<5`cV^L8>1EpA5?ymrUWi0-A_8jmd{E5H#a^hb}Tz{T;4-9Ai z-;}PGI4_AjzmWuC|3>S1Ir$|W<@e-9xL=e1MoxK&@RG>!8=>>nFNFVu)qlw1&%yOC zirP!Sm*j}wfNk*qS4&>)u5Fqgn8)QQh$sopa z33M+N<<~VT`isoZ@ymC8nG*d1_;nH>{qBab0F!U1iuWtD_BnU`6^`Bnp-%k4F^p~6JZvZs9KcxTXEmm0$ U5@^0Zw_jus05G`}qyIVjKiJn^X8-^I literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index 6747934a..0a2f5a85 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -76,6 +76,9 @@ class DataSeriesValues /** @var string */ private $schemeClr = ''; + /** @var string */ + private $prstClr = ''; + /** * Line Width. * @@ -262,7 +265,7 @@ class DataSeriesValues /** * Set fill color for series. * - * @param null|string|string[] $color HEX color or array with HEX colors + * @param string|string[] $color HEX color or array with HEX colors * * @return DataSeriesValues */ @@ -270,10 +273,14 @@ class DataSeriesValues { if (is_array($color)) { foreach ($color as $colorValue) { - $this->validateColor($colorValue); + if (substr($colorValue, 0, 1) !== '*' && substr($colorValue, 0, 1) !== '/') { + $this->validateColor($colorValue); + } } } else { - $this->validateColor("$color"); + if (substr($color, 0, 1) !== '*' && substr($color, 0, 1) !== '/') { + $this->validateColor("$color"); + } } $this->fillColor = $color; @@ -470,4 +477,16 @@ class DataSeriesValues return $this; } + + public function getPrstClr(): string + { + return $this->prstClr; + } + + public function setPrstClr(string $prstClr): self + { + $this->prstClr = $prstClr; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index 01a83915..6db04809 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -14,6 +14,11 @@ abstract class Properties EXCEL_COLOR_TYPE_STANDARD = 'prstClr'; const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr'; const EXCEL_COLOR_TYPE_ARGB = 'srgbClr'; + const EXCEL_COLOR_TYPES = [ + self::EXCEL_COLOR_TYPE_ARGB, + self::EXCEL_COLOR_TYPE_SCHEME, + self::EXCEL_COLOR_TYPE_STANDARD, + ]; const AXIS_LABELS_LOW = 'low'; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 55a150b7..8e3d6386 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -359,7 +359,9 @@ class Chart $pointSize = null; $noFill = false; $schemeClr = ''; + $prstClr = ''; $bubble3D = false; + $dPtColors = []; foreach ($seriesDetails as $seriesKey => $seriesDetail) { switch ($seriesKey) { case 'idx': @@ -383,7 +385,24 @@ class Chart $noFill = true; } if (isset($children->solidFill)) { - $this->readColor($children->solidFill, $srgbClr, $schemeClr); + $this->readColor($children->solidFill, $srgbClr, $schemeClr, $prstClr); + } + + break; + case 'dPt': + $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string'); + if (isset($seriesDetail->spPr)) { + $children = $seriesDetail->spPr->children($this->aNamespace); + if (isset($children->solidFill)) { + $arrayColors = $this->readColor($children->solidFill); + if ($arrayColors['type'] === 'srgbClr') { + $dptColors[$dptIdx] = $arrayColors['value']; + } elseif ($arrayColors['type'] === 'prstClr') { + $dptColors[$dptIdx] = '/' . $arrayColors['value']; + } else { + $dptColors[$dptIdx] = '*' . $arrayColors['value']; + } + } } break; @@ -394,7 +413,7 @@ class Chart if (count($seriesDetail->spPr) === 1) { $ln = $seriesDetail->spPr->children($this->aNamespace); if (isset($ln->solidFill)) { - $this->readColor($ln->solidFill, $srgbClr, $schemeClr); + $this->readColor($ln->solidFill, $srgbClr, $schemeClr, $prstClr); } } @@ -461,6 +480,16 @@ class Chart if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setSchemeClr($schemeClr); } + } elseif ($prstClr) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setPrstClr($prstClr); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setPrstClr($prstClr); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setPrstClr($prstClr); + } } if ($bubble3D) { if (isset($seriesLabel[$seriesIndex])) { @@ -473,6 +502,17 @@ class Chart $seriesValues[$seriesIndex]->setBubble3D($bubble3D); } } + if (!empty($dptColors)) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setFillColor($dptColors); + } + } } } /** @phpstan-ignore-next-line */ @@ -1001,39 +1041,31 @@ class Chart 'innerShdw', ]; - private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null): array + private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null, ?string &$prstClr = null): array { $result = [ 'type' => null, 'value' => null, 'alpha' => null, ]; - if (isset($colorXml->srgbClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_ARGB; - $result['value'] = $srgbClr = self::getAttribute($colorXml->srgbClr, 'val', 'string'); - if (isset($colorXml->srgbClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->srgbClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; - } - } elseif (isset($colorXml->schemeClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_SCHEME; - $result['value'] = $schemeClr = self::getAttribute($colorXml->schemeClr, 'val', 'string'); - if (isset($colorXml->schemeClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->schemeClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; - } - } elseif (isset($colorXml->prstClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_STANDARD; - $result['value'] = self::getAttribute($colorXml->prstClr, 'val', 'string'); - if (isset($colorXml->prstClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->prstClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; + foreach (Properties::EXCEL_COLOR_TYPES as $type) { + if (isset($colorXml->$type)) { + $result['type'] = $type; + $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string'); + if ($type === Properties::EXCEL_COLOR_TYPE_ARGB) { + $srgbClr = $result['value']; + } elseif ($type === Properties::EXCEL_COLOR_TYPE_SCHEME) { + $schemeClr = $result['value']; + } elseif ($type === Properties::EXCEL_COLOR_TYPE_STANDARD) { + $prstClr = $result['value']; + } + if (isset($colorXml->$type->alpha)) { + $alpha = (int) self::getAttribute($colorXml->$type->alpha, 'val', 'string'); + $alpha = 100 - (int) ($alpha / 1000); + $result['alpha'] = $alpha; + } + + break; } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index e24afbac..dda395ac 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -942,6 +942,9 @@ class Chart extends WriterPart */ private function writePlotSeriesValuesElement(XMLWriter $objWriter, $val = 3, $fillColor = 'FF9900'): void { + if ($fillColor === '') { + return; + } $objWriter->startElement('c:dPt'); $objWriter->startElement('c:idx'); $objWriter->writeAttribute('val', $val); @@ -953,8 +956,16 @@ class Chart extends WriterPart $objWriter->startElement('c:spPr'); $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); + if (substr($fillColor, 0, 1) === '*') { + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', substr($fillColor, 1)); + } elseif (substr($fillColor, 0, 1) === '/') { + $objWriter->startElement('a:prstClr'); + $objWriter->writeAttribute('val', substr($fillColor, 1)); + } else { + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $fillColor); + } $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); @@ -1039,7 +1050,7 @@ class Chart extends WriterPart $fillColorValues = $plotSeriesValues->getFillColor(); if ($fillColorValues !== null && is_array($fillColorValues)) { foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { - $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900')); + $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? ''); } } else { $this->writePlotSeriesValuesElement($objWriter); @@ -1061,7 +1072,7 @@ class Chart extends WriterPart $groupType == DataSeries::TYPE_LINECHART || $groupType == DataSeries::TYPE_STOCKCHART || ($groupType === DataSeries::TYPE_SCATTERCHART && $plotSeriesValues !== false && !$plotSeriesValues->getScatterLines()) - || ($plotSeriesValues !== false && $plotSeriesValues->getSchemeClr()) + || ($plotSeriesValues !== false && ($plotSeriesValues->getSchemeClr() || $plotSeriesValues->getPrstClr())) ) { $plotLineWidth = 12700; if ($plotSeriesValues) { @@ -1069,10 +1080,21 @@ class Chart extends WriterPart } $objWriter->startElement('c:spPr'); - $schemeClr = $plotLabel ? $plotLabel->getSchemeClr() : null; + $schemeClr = $typeClr = ''; + if ($plotLabel) { + $schemeClr = $plotLabel->getSchemeClr(); + if ($schemeClr) { + $typeClr = 'schemeClr'; + } else { + $schemeClr = $plotLabel->getPrstClr(); + if ($schemeClr) { + $typeClr = 'prstClr'; + } + } + } if ($schemeClr) { $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:schemeClr'); + $objWriter->startElement("a:$typeClr"); $objWriter->writeAttribute('val', $schemeClr); $objWriter->endElement(); $objWriter->endElement(); diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php index a193ca7d..3123278f 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; use PHPUnit\Framework\TestCase; @@ -13,17 +12,6 @@ class Charts32XmlTest extends TestCase // These tests can only be performed by examining xml. private const DIRECTORY = 'samples' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR; - /** @var string */ - private $outputFileName = ''; - - protected function tearDown(): void - { - if ($this->outputFileName !== '') { - unlink($this->outputFileName); - $this->outputFileName = ''; - } - } - /** * @dataProvider providerScatterCharts */ @@ -33,25 +21,21 @@ class Charts32XmlTest extends TestCase $reader = new XlsxReader(); $reader->setIncludeCharts(true); $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart2.xml'; - $data = file_get_contents($file); - // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } else { - self::assertSame(1, substr_count($data, '')); - self::assertSame($expectedCount, substr_count($data, '')); - } + self::assertSame(1, substr_count($data, '')); + self::assertSame($expectedCount, substr_count($data, '')); } public function providerScatterCharts(): array @@ -69,23 +53,20 @@ class Charts32XmlTest extends TestCase $reader = new XlsxReader(); $reader->setIncludeCharts(true); $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart1.xml'; - $data = file_get_contents($file); // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } else { - self::assertSame(0, substr_count($data, '')); - } + self::assertSame(0, substr_count($data, '')); } /** @@ -116,18 +97,11 @@ class Charts32XmlTest extends TestCase $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart2.xml'; - $data = file_get_contents($file); - // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } elseif ($numeric === true) { + if ($numeric === true) { self::assertSame(0, substr_count($data, '')); self::assertSame(2, substr_count($data, '')); } else { @@ -144,4 +118,31 @@ class Charts32XmlTest extends TestCase [null], ]; } + + public function testAreaPrstClr(): void + { + $file = self::DIRECTORY . '32readwriteAreaChart4.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + $spreadsheet->disconnectWorksheets(); + + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + } } diff --git a/tests/PhpSpreadsheetTests/Chart/PieFillTest.php b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php new file mode 100644 index 00000000..452fcb93 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php @@ -0,0 +1,160 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testPieFill(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + // Custom colors for dataSeries (gray, blue, red, orange) + $colors = [ + 'cccccc', + '*accent1', // use schemeClr, was '00abb8', + '/green', // use prstClr, was 'b8292f', + 'eb8500', + ]; + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels1 = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_STRING, + 'Worksheet!$C$1', + null, + 1 + ), // 2011 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + // Custom Colors + $dataSeriesValues1Element = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4); + $dataSeriesValues1Element->setFillColor($colors); + $dataSeriesValues1 = [$dataSeriesValues1Element]; + + // Build the dataseries + $series1 = new DataSeries( + DataSeries::TYPE_PIECHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues + ); + + // Set up a layout object for the Pie chart + $layout1 = new Layout(); + $layout1->setShowVal(true); + $layout1->setShowPercent(true); + + // Set the series in the plot area + $plotArea1 = new PlotArea($layout1, [$series1]); + // Set the chart legend + $legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + + $title1 = new Title('Test Pie Chart'); + + // Create the chart + $chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // no Y-Axis for Pie Chart + ); + + // Set the position where the chart should appear in the worksheet + $chart1->setTopLeftPosition('A7'); + $chart1->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart1); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $plotArea2 = $chart2->getPlotArea(); + $dataSeries2 = $plotArea2->getPlotGroup(); + self::assertCount(1, $dataSeries2); + $plotValues = $dataSeries2[0]->getPlotValues(); + self::assertCount(1, $plotValues); + $fillColors = $plotValues[0]->getFillColor(); + self::assertSame($colors, $fillColors); + + $writer = new XlsxWriter($reloadedSpreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart2); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(4, substr_count($data, '')); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +}