From d12935735cce7450f6af4e0c3c2567c8693cc3ec Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Wed, 1 Sep 2021 08:21:53 -0700 Subject: [PATCH] Deleting Sheet with Local Defined Name Fixes issue #2266. Writer/Xlsx fails when there is no longer a sheet which corresponds to the definition of a local defined name. The code is changed to drop such an orphaned name. Writer/Xls does not fail under the same cicrcumstances, so no correction is needed there. Writer/Ods fails in a different manner, and is corrected to no longer do so. --- phpstan-baseline.neon | 40 --------------- .../Writer/Ods/NamedExpressions.php | 22 +++++++-- .../Writer/Xlsx/DefinedNames.php | 28 ++++++++--- .../Writer/Xlsx/Issue2266Test.php | 46 ++++++++++++++++++ tests/data/Writer/XLSX/issue.2266f.xlsx | Bin 0 -> 9586 bytes 5 files changed, 84 insertions(+), 52 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2266Test.php create mode 100644 tests/data/Writer/XLSX/issue.2266f.xlsx diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c997788a8f..f4571154dd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5300,31 +5300,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Ods/Formula.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$objWriter has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$spreadsheet has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$formulaConvertor has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:__construct\\(\\) has parameter \\$formulaConvertor with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php - - - - message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" - count: 3 - path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php - - message: "#^Parameter \\#1 \\$content of method XMLWriter\\:\\:text\\(\\) expects string, int given\\.$#" count: 2 @@ -5885,21 +5860,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\DefinedNames\\:\\:\\$objWriter has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\DefinedNames\\:\\:\\$spreadsheet has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php - - - - message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php - - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php b/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php index ae1c4217b0..e309dab117 100644 --- a/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php +++ b/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php @@ -10,13 +10,16 @@ class NamedExpressions { + /** @var XMLWriter */ private $objWriter; + /** @var Spreadsheet */ private $spreadsheet; + /** @var Formula */ private $formulaConvertor; - public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet, $formulaConvertor) + public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet, Formula $formulaConvertor) { $this->objWriter = $objWriter; $this->spreadsheet = $spreadsheet; @@ -51,23 +54,29 @@ private function writeExpressions(): void private function writeNamedFormula(DefinedName $definedName, Worksheet $defaultWorksheet): void { + $title = ($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle(); $this->objWriter->writeAttribute('table:name', $definedName->getName()); $this->objWriter->writeAttribute( 'table:expression', - $this->formulaConvertor->convertFormula($definedName->getValue(), $definedName->getWorksheet()->getTitle()) + $this->formulaConvertor->convertFormula($definedName->getValue(), $title) ); $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( $definedName, - "'" . (($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle()) . "'!\$A\$1" + "'" . $title . "'!\$A\$1" )); } private function writeNamedRange(DefinedName $definedName): void { + $baseCell = '$A$1'; + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $baseCell = "'" . $ws->getTitle() . "'!$baseCell"; + } $this->objWriter->writeAttribute('table:name', $definedName->getName()); $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( $definedName, - "'" . $definedName->getWorksheet()->getTitle() . "'!\$A\$1" + $baseCell )); $this->objWriter->writeAttribute('table:cell-range-address', $this->convertAddress($definedName, $definedName->getValue())); } @@ -100,7 +109,10 @@ private function convertAddress(DefinedName $definedName, string $address): stri if (empty($worksheet)) { if (($offset === 0) || ($address[$offset - 1] !== ':')) { // We need a worksheet - $worksheet = $definedName->getWorksheet()->getTitle(); + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $worksheet = $ws->getTitle(); + } } } else { $worksheet = str_replace("''", "'", trim($worksheet, "'")); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php index 4c0929dbb2..53706bde59 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use Exception; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\DefinedName; @@ -11,8 +12,10 @@ class DefinedNames { + /** @var XMLWriter */ private $objWriter; + /** @var Spreadsheet */ private $spreadsheet; public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet) @@ -66,12 +69,22 @@ private function writeNamedRangesAndFormulae(): void private function writeDefinedName(DefinedName $pDefinedName): void { // definedName for named range + $local = -1; + if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) { + try { + $local = $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope()); + } catch (Exception $e) { + // See issue 2266 - deleting sheet which contains + // defined names will cause Exception above. + return; + } + } $this->objWriter->startElement('definedName'); $this->objWriter->writeAttribute('name', $pDefinedName->getName()); - if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) { + if ($local >= 0) { $this->objWriter->writeAttribute( 'localSheetId', - $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope()) + "$local" ); } @@ -92,7 +105,7 @@ private function writeNamedRangeForAutofilter(Worksheet $pSheet, int $pSheetId = if (!empty($autoFilterRange)) { $this->objWriter->startElement('definedName'); $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase'); - $this->objWriter->writeAttribute('localSheetId', $pSheetId); + $this->objWriter->writeAttribute('localSheetId', "$pSheetId"); $this->objWriter->writeAttribute('hidden', '1'); // Create absolute coordinate and write as raw text @@ -105,7 +118,7 @@ private function writeNamedRangeForAutofilter(Worksheet $pSheet, int $pSheetId = $range[1] = Coordinate::absoluteCoordinate($range[1]); $range = implode(':', $range); - $this->objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range); + $this->objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle() ?? '') . '\'!' . $range); $this->objWriter->endElement(); } @@ -120,7 +133,7 @@ private function writeNamedRangeForPrintTitles(Worksheet $pSheet, int $pSheetId if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) { $this->objWriter->startElement('definedName'); $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles'); - $this->objWriter->writeAttribute('localSheetId', $pSheetId); + $this->objWriter->writeAttribute('localSheetId', "$pSheetId"); // Setting string $settingString = ''; @@ -158,7 +171,7 @@ private function writeNamedRangeForPrintArea(Worksheet $pSheet, int $pSheetId = if ($pSheet->getPageSetup()->isPrintAreaSet()) { $this->objWriter->startElement('definedName'); $this->objWriter->writeAttribute('name', '_xlnm.Print_Area'); - $this->objWriter->writeAttribute('localSheetId', $pSheetId); + $this->objWriter->writeAttribute('localSheetId', "$pSheetId"); // Print area $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea()); @@ -205,7 +218,8 @@ private function getDefinedRange(DefinedName $pDefinedName): string if (empty($worksheet)) { if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { // We should have a worksheet - $worksheet = $pDefinedName->getWorksheet() ? $pDefinedName->getWorksheet()->getTitle() : null; + $ws = $pDefinedName->getWorksheet(); + $worksheet = ($ws === null) ? null : $ws->getTitle(); } } else { $worksheet = str_replace("''", "'", trim($worksheet, "'")); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2266Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2266Test.php new file mode 100644 index 0000000000..68583be083 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2266Test.php @@ -0,0 +1,46 @@ +load('tests/data/Writer/XLSX/issue.2266f.xlsx'); + self::assertCount(2, $spreadsheet->getAllSheets()); + self::assertCount(1, $spreadsheet->getDefinedNames()); + $index = 1; + $sheet = $spreadsheet->getSheet($index); + self::assertSame('Sheet2', $sheet->getTitle()); + $definedName = $spreadsheet->getDefinedName('LocalName', $sheet); + self::assertNotNull($definedName); + self::assertTrue($definedName->getLocalOnly()); + $spreadsheet->removeSheetByIndex($index); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type); + $spreadsheet->disconnectWorksheets(); + + self::assertCount(1, $reloadedSpreadsheet->getAllSheets()); + self::assertCount(0, $reloadedSpreadsheet->getDefinedNames()); + self::assertNotEquals('Sheet2', $reloadedSpreadsheet->getSheet(0)->getTitle()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function providerType(): array + { + return [ + ['Xlsx'], + ['Xls'], + ['Ods'], + ]; + } +} diff --git a/tests/data/Writer/XLSX/issue.2266f.xlsx b/tests/data/Writer/XLSX/issue.2266f.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c0d0e5a998423473918d6d9704b379a64c4314bf GIT binary patch literal 9586 zcmeHtWmFv767Jw02*H8`$l%UEf(Mrw+}#}pC%6*`?(QT&NRZ$XEJ$!C1b26L$(!Wf z^Kwqkx$ocmb#JdVz1OVis@c8w_f=JY)e6!GhS4xy!m?-CfVV&x1pv)L8@)s7}PUx6rT{wC&xmL*5M^-?c}c^hJt+jA;q%&dbOwS2ULE-NdxC7+G;(oE+nKM z_RueAoQ21cqaukHaA;UZW19pX$%!4_VF^DHB{ zs%T1HEbTs>Blz4$F^6}Of6hFf9EPgz-w^-`f1zfzDjV<=_KOURc^EKi>N}c3omg3Z zoc~A3|6(5g>DG(mWaZo0FoO@IZeMp_jL*g3h)BB%OEplc`1pZmv8yBBQjyHH(30Y) z5CO7whFH{z(H`ZK1``OR`fGK{YR(&7mcosTDDRa7m(_CR zY{w6!`%WYkY+(t8@W||rrs8)y8JSO&dUZo7&#s77l`VKI%Z#!dd7rxLn?OIGh^4nY zy7OX|%ji~m%87dk8j|dy&N=hds^UEA%XIJHLTfMTKI{nyCoi0bWvRcDM0oELZX^N# zFat|x*szsxgR;8XIa(Xp*;)U{UB#-pc5k`yy!9==BZRLf23#ROdyIrS4cRNSs@&_7 z0up8z;M;#op;3MI-Mds)D!#`maSxh!J=3)@@43Hrd{Xniny`(pcRDnRg-I7^(+Zg` zrMcD*pGAFH@~Dp4VL%Hsq7Q!On{|Ibvle``LED-23}Dd^s)tq{C0ohJFuJ%OPy)u_ zlkOy8GU!z|t6fJ-))f!2uo|mjT7a_IoWW79Zy1!V#Su^$i!a2eVNzs#)d1X;zS*%A zvkr4wB<|0?sa7mA!@`lTDp*$a-_}y@P%lbyxK#<9(q6UHejrGGABz+z(2S@}EB~x& zvDjGJ0d9C*=(@wlBo&pc-oSG^@FYejKAw=lxX%2`9d^a>g~mM}QH9TAEdQzmH6#0Z zl#xaOF70eR7E9SD$Xix!P4@vLkKb!eSj~pIgOTWIJpjrzl#&>Y`m_Qih7&HafzD2! zl24c?AQc_n%mfsx51)YAFMu=gw8BJ{S~&AY!b;!JhnEIR^~W>XtE+ek3{$?qzw=di%97_!Gs$I7)G0WFuHwf3VNcB- zt5bdIncMA?7QgS5y=yE2A3wJ))pD^`oeU2lv^$*i+XqrTd%DnzB`6)pR7I9BSaD#0 zbg>e!q^Hu50_hLk7XirE*dDsf3aibI9pBw?SznlU-@h}PyWZPaFl)h;P&I)p3%_K3z z;R)Um_h%Q4G8O|H1U(FcnxKTmx(k8F!8rvUC92cS$<>d9g_D|2St8t-78cd>6CBA*ygiqRExVLg^sJJ^ZO~>jty!&FVpbrk{Up%28AJ*hy_;A??;yBJ@uT}r2$@1 zhD&;IThx3npNS-qfr(8ROZjp=6rLkRQ%FL3r$wCON$vt%tvyGbUY?F-HJcF`OPP^G&W8u18?3@F;x(N#oH z9qG3BId)+vBn>v*W)^e$UbP+*rfolOee-&hq9`D(t75>l=;q5>Sy}j^B3Z}*XzOBb z&zH{)``g57fBRK?D#{ouS9m(dklqmC!V{l*;F zK}M@bJzm30P)%}9sPCv(f!3%;X>j`JqYyU}+QOWah*7{n*qg_BCk`?6eUm2?<9g@a zI~87wN3O`6=Bdyb^pKfzf=OG<`l|!z!b^#zO7#PTPtLgk@A`vy6gr>8`vvl+sm2QX z>zfrOn_Qkc$P=??a?^xg8Y&I7s(i=f@Jzj-HcY7621~EVC8rkGhtD#j30qcOb-g3! z+0ORxIX@rZg6;R;Lw>OlWs(o9CQ^nyvJkXkt8F8B7Gi-Q4 z3mA9UA_q0Q9S}UwRdsO{iq{LCdog)2$!rw<-wtMhna5{&_&k0m zqiJCU4kBnohSruf_;**vWK9ao2wcz9zX^#CEpF}Xp1eD@m>aAG_*1lAhSg9yBN^*e z<kvSNOv#z-f{vSI&=RCwiW`g9)#!Qg8DsE0QkTn;4gK-fwDaHLfKL^im48 zP?6sG8slEB^U>kJDFE~HqBlCHb$t)Xl5wf7mxSb3pLQcK3DF~Nq#P~~Mhg4Q;hC*@ zd#P$;``>R%VPP#49S*<(_iPQEX(hMlTW_VX>qbOYwl{QS(dGNTKlkjr}sVLF0NcA%!R}}m0qKn3Nm=q;IlD{smer?3nrbZAjrshjGiBXai zr6}qj?3ZZiACwS$9O7^G84L8Zl>%5MygrEqqG_dwnd#kSDkCuw2@s|+2xKZJF@XdK z7Z?O$VhziTO1|R%^CYBXTeZ50&kR)!Dr2)Due#8Lpjg4UhFgMY z^m#e`F+Wq5yjCoN2Cc06Fn0u*~N4cY+-q^p*B8?a0l_@k5Dt=3LgtQPkNT zS+d?pSh*QbyZs!smj22Fm{ntq}=$ z44AUG@g+(Ox=P{k$|n$LBIapC;x05!7(#~SUku_CCAkCfk=&US7Cw^V92le(t13!G0OV9Zx4Ap*u->IE1nczS~U5p$+-bGcjJ|`j;0#DCsF~#5NwkeI#8S8(&uvgYQ0*g_ zAJ*5ctS9>r9W;@n;#(F>j`?X(9OI)f{R^G;*O_MT%y%Yo@btCYC#3iE6lzbc6~w@f zb-^q_)mEKcSq@+>}Uv-k1wRy^T?_t}PgG}-hFDOt)PWoY?f>5}mQO}L@|gPS8b z!ORXek&ZZ_hnp^i`+eQ;dfF7*Z7<9A+Ga>KI*}UrTdb!(kssAlMWud}C`Xz#j&LDz zlj~M$ivgUA;Rgj}^t0YY+E0vf>82!n$G1DvQb>7!!#KJA;m@5x% zp6GTwkCS%X9IX#0;)oS+nT%B6cbb-o>+bNOc|a*1>lz3q^opM16B7_6Rj6Q}twpP8 zyEA)QhqP*ymOvYFZ8}I+-WZYEL{8`&P!?`F&0jNFJrK&l$9wWVak_y>**T(<$g}o^ zlg6+iN8d*Mi2755M6z8-F=O|j>RE;XoN3$#>u>aq$*7E`7e@&;W#)Dc0d53zp$`}G zu876vAiCUZ87dx`Q4eGnpTVz@uZEA&Patiw#Wl2mcOp?W&67O{_PuoN8GY<3yA1T_ z1@@W|AA4CnrZ}%XAHY$x4&XNoMn%UYoC^rSzt<7VEYgDRC!|lNKgUMElTd4V6u%nz zWhk_Q_SUuci7T8FGE%hm(M)f=&KBm#x4a7OVH zZM_xT;M2>rXDLjx2|(r*2O2?I&~)@Lrk1Gb+~S3m{x#;u}c1(d_(``uZzK zzTT*w#F1k=68oXG_*2?Ce;OL);SN6At7CSq}pkiM~DRQJPqp|z4G#7Lg*faA>9 z?FMQES_aP%DQ3x$`!6Y%+TVZpvOrc$HAULiL>?L4guuJO^)OFEgOw2_D%3AL&~bZ! z-9NnyKX&cPb=jcTVxlo@lqVqy$Q!QU@+QS3tL<56Y5#~kBRP@$R9b894hw(E2ZZw` z`8h7~@m?on%XRaDFAUpXo#VG&^Q~eP9gBXgV92+l!V0UJ})=MF#9Z2Tob?VGE8*pi=I` znxY^VBDKD|GM(+04A_eXv5Gnm#&)jZbPR{ZT`WI#O|a~*tT7J+Jq!<*(eEd$Ub$z9GJ1*5US8ojqkGvLCiq2~KPDqZfme$YHv38{n( z0AT$q{W>|jLrtB2)Qx$nHr6w2IQK6meQ~b$dtFwT&}#Zdzbb5Y=8)|c8TEx0>P1kQ z4|d+)$hqi|^45&_gvxD6WP@8?;J0D|cM3lg z4u_e#4KX$MEVM|M%wU|?nf6oqDLil2-bh2DteFLY_Y!SW0$8h`$MH_4VUI3ROrmwB|LX#_Ol|CwFv!WVP*6+ z)i`ec=4te5{)pPdKDvEqskL53vh#+CoQ1mt{OSayI2zhY_%z=MBoB=p+STO6EpJ_t zA)arWh`{b&XxF-|xpna2FuTzyq*wU{VXqJ%{|cOyq-7CEDut4{BM=68E;g&wIuU$R zmIh%t59B+K(%TcwSRM8}pvvkpEVmEAa4^F6gkS7dju~Qqi$0kXcvodd(G&nCF}SYt z6|=*flXL*_3SGZ)|MaaOpy$QK%4V-}s}+hE)&lfy+Zmz7dXKa)`yKu}l9Iy1d`t>j zgkJhooM@lxmT|&K-AENijII45%yxleS?)B2?pD&n;K(lWw^);Q!Q#5!q1Yk7X~I1D zt=I{^_UyZt_-s5Vs8zQ0_Bwa|YzAs`^+>tupRbB0xHb}ycooajYT5)cUzlA&eU0$# zOufRc+gzv5a2~&y!Pa|kvF%hgD4;>as-bA@7VJCEEZDE^Do)zpw#JmAQ%RLV+ou{M zW1C#0%8tEqcs{WS=hl99uu#Mw6kc5h{{+p6?p$GcWh!}T#4KlFh;I0aQ+C(bp)qcd zumhPys|m!s73z_RD31H3Xv@Ss8LubA^SIaiDU~88KX;Z_g}n}_bzHINt%xye2;xi& zo^TW-<|JGAp5&jNh_I)v+Y@0heh>TL{0q*;hEQWs3qwoWABg6U5=L)h!x6s-a7C@G z9dn_43PwlI62itA9YKr(_aKhUq$l=uy}roSd%^RbOEokIOQsz)Y%=-Tao^_^AHEDB zuv6nFl@v@butE7v<~hriwSBBAR~Zp%iH*6~k$**eF0{kjJt<9hM?;@Zo4OW4Va2n= zA0^qZ3sCb^#nPcs82OCJaE2>XbmVLFuLsuO8#E}JI{;A_S1nwvk6qmRlaxo~QXKsICKGT6TMAqKUd z%gx28OVRk%A~3^AaIl^-`#=@nJK$x-HzdfTfTzQa1WI-or&W3#9C$oKRR*dhxk4!R z7J7PmtFsDv+HPgla68m>bTL(LXYnjHnsSNwni0O};F9u&q)#)fw~yGC8{THxz?r|2 zDTg0IJi)B}Jc%jhEQbDqV&HWNuiqjOtmoI%ZW*_1_YtLNHX9)mT?i$r(!a`O0bc@eSn+613&@!#>Ya6kGMTuLDCw*rq4A>4)NH8qVMoANP!=^e>}nzMDwENgL3c+3@YJ z6~4iEiFxF^oKT<+Tt-t^zua|7(1$JG-{k~X`(5Nx81kxM+9}STk!NUc|3Bctmh9h0 zhH|UiEE`Uf`ZBrFf~GlKuGJcmDh58vbm=FI3|;eza;e?!CvWC_yt^FG;MAl}pJ#D- zoU9LM=PE;0Jx8MHriYSK+Ufp3VB%k*RKKApoY<>G29 z18_%$k}RM#`_VG73a3qe1h&#?-KOx{6HPYXZkY0Nrv~>XBjBu;#5C6AOT{VCaskHX zTGI)3F7jszZv4De969TE}OAA&$P`MU%!MvZ*x#J{GP<Zkv1_yO-AR9%{-tjC z-TJRd`j;&Lz)uDM{4HhwZvNLu`)BhS@;{mXJMJn-Bf;?T<8=)TKpU(oNPGI@+y4P` CshL;+ literal 0 HcmV?d00001