From b3c2881f02a1d98b338f60171eb208bcc83bb4b5 Mon Sep 17 00:00:00 2001 From: Gianluca Giovinazzo Date: Tue, 18 Aug 2020 10:25:25 +0200 Subject: [PATCH 01/52] Fix for Xls when BIFF8 SST (FCh) has bad Shared string length --- src/PhpSpreadsheet/Reader/Xls.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 81cc5b2eda..2498c56bf2 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -2973,6 +2973,9 @@ private function readSst(): void // offset within (spliced) record data $pos = 0; + // Limit position, further control for bad SST Length in BIFF8 data + $limitpos = 0; + // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); @@ -2986,8 +2989,19 @@ private function readSst(): void $nm = self::getInt4d($recordData, 4); $pos += 4; + // look up limit position + foreach ($spliceOffsets as $spliceOffset) { + // it can happen that the string is empty, therefore we need + // <= and not just < + if ($pos <= $spliceOffset) { + $limitpos = $spliceOffset; + + break; + } + } + // loop through the Unicode strings (16-bit length) - for ($i = 0; $i < $nm; ++$i) { + for ($i = 0; $i < $nm && $pos < $limitpos; ++$i) { // number of characters in the Unicode string $numChars = self::getUInt2d($recordData, $pos); $pos += 2; @@ -3020,7 +3034,7 @@ private function readSst(): void // expected byte length of character array if not split $len = ($isCompressed) ? $numChars : $numChars * 2; - // look up limit position + // look up limit position - Check it again to be sure that no error occurs when parsing SST structure foreach ($spliceOffsets as $spliceOffset) { // it can happen that the string is empty, therefore we need // <= and not just < From fa6b2deea12e90f5a55ed756a7728f5f404a9979 Mon Sep 17 00:00:00 2001 From: Gianluca Giovinazzo Date: Tue, 18 Aug 2020 11:34:55 +0200 Subject: [PATCH 02/52] Fix and test for bug #1592 --- tests/PhpSpreadsheetTests/Reader/XlsTest.php | 26 +++++++++++++++++++ tests/data/Reader/XLS/bug1592.xls | Bin 0 -> 20992 bytes 2 files changed, 26 insertions(+) create mode 100644 tests/data/Reader/XLS/bug1592.xls diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/XlsTest.php index da39f8b2a3..9d14aee9d2 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsTest.php @@ -43,4 +43,30 @@ public function testLoadXlsBug1505(): void self::assertEquals($sheet->getCell('A1')->getFormattedValue(), $newsheet->getCell('A1')->getFormattedValue()); self::assertEquals($sheet->getCell("$col$row")->getFormattedValue(), $newsheet->getCell("$col$row")->getFormattedValue()); } + + /** + * Test load Xls file with invalid length in SST map. + */ + public function testLoadXlsBug1592(): void + { + $filename = 'tests/data/Reader/XLS/bug1592.xls'; + $reader = new Xls(); + // When no fix applied, spreadsheet is not loaded + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + $col = $sheet->getHighestColumn(); + $row = $sheet->getHighestRow(); + $newspreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $newsheet = $newspreadsheet->getActiveSheet(); + $newcol = $newsheet->getHighestColumn(); + $newrow = $newsheet->getHighestRow(); + + self::assertEquals($spreadsheet->getSheetCount(), $newspreadsheet->getSheetCount()); + self::assertEquals($sheet->getTitle(), $newsheet->getTitle()); + self::assertEquals($sheet->getColumnDimensions(), $newsheet->getColumnDimensions()); + self::assertEquals($col, $newcol); + self::assertEquals($row, $newrow); + self::assertEquals($sheet->getCell('A1')->getFormattedValue(), $newsheet->getCell('A1')->getFormattedValue()); + self::assertEquals($sheet->getCell("$col$row")->getFormattedValue(), $newsheet->getCell("$col$row")->getFormattedValue()); + } } diff --git a/tests/data/Reader/XLS/bug1592.xls b/tests/data/Reader/XLS/bug1592.xls new file mode 100644 index 0000000000000000000000000000000000000000..02413abcc2d884a2cb2d12810dbd849d0860f4fe GIT binary patch literal 20992 zcmeI)du$cieFyM!uiw}PY=bezkBcApfo+T*7%;{**ch)vX-Rw41qg3sx`^PG)ZbI9rt@2m@=r)^%UHbd|=AJ$C zz2|JTRZ_K$a<9hs&TnS!XXea#&D`Vr&flawf8+Nv|6WBr4yq{i$;$*4>x&2I{SH09 zQPJ|3FL_qK7SVeO{qphuCmt2jBebiz|rklr@X>k2({>w63K%A$Ze z9n6zuyH%$8ONzy-S2dX%N_i?yWt){MD)Dn@odF6Su-$TVsL!?xw5_;06{_RRb{*6jQutlRPQ~MF~jsQK`H6Pj7 zXwW@sC!Irs>g_*u^~OT`x7@D2s@AK(4dp${)to9 zWZpRr)Z-^ii}X^UZSi$PS0gZ=q`#bs0o@|qUSWd zpO+djq^fDFYFcALun^6iq>t^TNEN-Wq(U1~{j}ZUr`t8q`rcN&yn;$(r92ZARki%_ zO!g5lt*)r6G#9G^E%fPgBu-P9W;>QOPRMOB?a0adOPlv`FhO)9aX`v}>Ucs8qE~b*UcN zq?I;nRvq+grhSQ5fdSb!K59OiYR(WZ)jC>INSTAvdmT+)qqGqkrNm$XE%6?&gkMLg zuAePZYN<}CTWw0!wkygK@l<R^KAI%gS6&TJ^*^~@+f-xmq$}79oTA?$577i z@G`BeA4?}qFIPbi@{To*Hi{2YQ%#RJ^>S6-(9Uy=k0CO~M-&;0a>Sw?F)pQ%+j4P< zjPX82#^N0@KETNJIFTY_iH;cOh{*Ml9WhR;$o077M#eY=BV#F!SgIqI=7@1y7WsLb z9kFyrj54paZH6Pp879)mbi}qfV$|8Owxzy}70YqNawB3NOW9Ovv*b8$E>;Hytx>f<2>cf#nK$-CZ6+3B?o6L0U8dMyP-))JRwBL(HC zv=p?YAV@(vvUGKL{FSe1iT9H`%iexRffNN2*BIuQqAV#2q$rV!zOwlXTH^FJ64!nv z^`b$F28nAXOucB3qD9JiR#B%VPFo|zfD{8#3`jAS#I>L~Pcb6>R`PPDmZYivH1()i zqqX&UiUlbaq*zOe1&M3hboG|{(Z_M&9Km_e922#FJ}C~QIFRBjDGsDKk<#98|7|UC zUN921i#{nHq0|QxL^}00g0vB&jh3_#q>Uogy)*rGOgQgxZ^0Z>3LH}kNGTwtSW*f| zDI#SieDq99+y^mIDoEZf8eN`JK}xlxRFG0d`lR{s6D@Ji#z<)(rNJ?!fs|%RX&|ME zw6*rdoR++6e|?_3D}H^RHi5JWq)nE@SB<9C*d)@6xhLIP;$E0JCa=$;C9lt-Q_t(O z=+>S4EZ7oy{TAJi;C@TGdj2e7l68D+SF=c|}T@~uo%LFOYk}^Tc6zR>n-+QJd z?ui>|8%W+&sFt>Yw9S&XfwWDeuU>9=q9yL%8z~EC~MVkGGGu>L^YY`*mfRqDL4oEqclmk+ZNOwN?m+wb~ zb2(q(7%3N|T##}>%C)3ika9&DPQU&ZOYhM=kUyfP@KW%in)w-?e61g%{f)d9-0q0w zIb!*aSb-x}=!orb#EKlTVn?jR5i51Xb~<8Zj##-PR^f>4a>ObfF}l@lr77L6w_<#= zDl+YB9I;wQY>y*GH?6GC<1bnwjeU+7-Ql;^t9Qf>IARTs*g;2(Z-n{xg|C@I6f-3z z1SKW}B_;$VCS;YE5R{mZl$e&zm}k0da;`Sgc96D%v>l}Fmb4ut{t75vy;eNgr6n#a zM#=-p`?^cl)_EZ1SyCQIc_OWhd|0g|E`dhM2Pq$pDIcVKOUef+U!=j;*S2Vh%e9dT zKq>&K0Hgv-DgdcKq<=3S>CzkcvfmnDp8QTH;#RNF^YZfK&ofi6xbQR3g$;YyP*i#C5xoNT#6QrFW?X;wwAng>XCiP#xpe1gHj8q0v8AxRym0405NM#~* ze{bY>w8X8Ok;*|T2dNySa!V=)sa&MFr|8EQ+eOlrkj*+TC zss^bVq-sm52B})4(eLc)PYkCX_nVAV15yo0H6YbkQVmEoBHerSjYTbSZ_G%wAk~6Y z3sS8m)q+$j(w7I)zNjVc;~8lWNP9rq1JWK#+5^%ak$&>dsX$UV^|Jl7^wlI29O#+YOtgRkQzi9|I*CY zwZzvXMmh-6L6CT`qPh2e(2@>-Y6PiKq_2cd z{u4`lAK*>75Ad_x1(=cV{F~@_{nuuEZ=l%`JLHHRcEnm7u~tXyh$GhKh#hsrjyYn- z9kF&ttiusI;fS4d#5x_ZE=R1}5$kcpPB~(|j#!@~*6)Z7IAW(Au``a?Sx4-gBR1%W zop;18IATK)F}@GbL@`q~o1nxrL5XRC64PXrm?kJOO;Te1_@DnQxISE4bFMa0Gf2%K zHG|Y_NzEWNi}c<%cIRq|%ZiZ>fpiF@Lm(Zpq(dMb5{d3`{+gD!1RCiuNQXf>4ANms zIt!j% zt}l&r6r`gd9R=yAB^?Fns7U_N6kH1%=@>}IKspA}F-tlI(lL?zqbayKc+aV)$fYbp}2S^>3)B#e5NdD0j z+`1X*1V|@9IswuNOF9A436cDxDY(rv(n*j`f^-t3la_Q6q?01~M^kW1Y@|+*Izj3L zsne1=LFyFAKbnHucO!Ly)CE!(NL`lH1yYwt{?QcNYcNtbNZlZHgVb$F-5_;~Kcd$~qB1JW6g z&VY2rlFop1MkN1e3hp}_=`2WRK{^Z4SxY(#(pi!GqbayYZlrS{odf9{Narl+97yLx z@{gwAe!h_gK^g>U5Trp%8U$%jB>!j%zIrgyd63S7bRMMhmUJGZ^CJ02Q}8v3kuHFA z0i+8cU9hAJAYBm2KbnHCbc{3v(hx{PAPrg45J*EJ>CqHF&-(zww7-$}0Y)6Li;mc+ zBR1xUjXPo!j@YClHsy#-J7P1A*sLRV$q}1##O58b1xM_%BX-3RTXe*h9I<6b?5ZPn z%@Moqh^;tcHyp8>j@T_n?6xCz#}T{hh~0~b@qK_{ikT8K3?*h5O3W~nm|?5L3`2<- zmJ;J16~wvPNFyMPfHVTqh$W4HG$N9JR1lXHBV7dPB1jiOx@bukLAofC9u>r?$0g86 zqaclfGz!wFC5?hKDw2OR1($0hdH?sMug=Fn8Uty}lEy$96UjfCf@=#Sjl(gGgES7( zxFwB)#Q)!xuKc4ZxDGPX1V|GgO@K6ENfRJVh~ytl!8MzaCPA76X%eJKOPT~}QY8Oq z3a&4WGzHQWNK+t9S<)0pQzH3CQ*bS8q-l_*L7E0>+LES0nik1Fnu6!j%ZZnNE57InH^B~Pz(mY7~uc+zDKbnGDVk0epv;fiq zNDG#<0Mdd;{?QcNz8mQ>NS8sn4ANywx(w1~k^G}6xYuB$D`{G%zjmusXOAl(4z z21qw7=>|wQMDmZO;J&kwZh~|Zq?;h!w4|FL-4w|`nu2@eM!E&kEs$=3bjy-%fpkkG z|7Z&C=NsuZNVh?{4bp8(x((88k^G}6`0Bw(cR;!W(jAcQSkfJk?ug_cO~KbBM!E~q zU6AgAbk~yZf^=6T|7Z%n(lOFKknVwW52SmRbPuF^BI(f-KhOIBtF*t7_W|xZVhLE_(hF!yxt zgLL1L?t^q+B>xBzPB$Yx0Oy+-oO6uy2&6|KJp$>GB|QS^kx2e;);T{K=`l!; zL3#|*V@rAr(qobIH|y`wFM}*bDf%fG|45#L!Bq7JvFtbTANq9>emtd59;4?vnfpKY zr-yzqW`TafB}BhQ!@u*gNI%{5-+A!AEb5;-3w+KE_3y>;>I0-Ns#$eW_QSh?` Date: Fri, 21 Aug 2020 18:50:11 +0200 Subject: [PATCH 03/52] Fix SST global limit count and improved bug test --- src/PhpSpreadsheet/Reader/Xls.php | 54 ++++++++++---------- tests/PhpSpreadsheetTests/Reader/XlsTest.php | 13 ++++- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 2498c56bf2..5caa865a08 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -1290,7 +1290,7 @@ public function load($pFilename) } } // Named Value - // TODO Provide support for named values + // TODO Provide support for named values } } $this->data = null; @@ -1302,8 +1302,8 @@ public function load($pFilename) * Read record data from stream, decrypting as required. * * @param string $data Data stream to read from - * @param int $pos Position to start reading from - * @param int $len Record data length + * @param int $pos Position to start reading from + * @param int $len Record data length * * @return string Record data */ @@ -1670,8 +1670,8 @@ private function readDefault(): void } /** - * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, - * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. + * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier + * versions, this record stores a note (cell note). This feature was significantly enhanced in Excel 97. */ private function readNote(): void { @@ -1718,7 +1718,7 @@ private function readNote(): void } else { // Set comment for the cell $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText)); -// ->setAuthor($author) + // ->setAuthor($author) } } } @@ -1850,7 +1850,7 @@ private function readFilepass(): void /** * Make an RC4 decryptor for the given block. * - * @param int $block Block for which to create decrypto + * @param int $block Block for which to create decrypto * @param string $valContext MD5 context state * * @return Xls\RC4 @@ -1882,11 +1882,11 @@ private function makeKey($block, $valContext) /** * Verify RC4 file password. * - * @param string $password Password to check - * @param string $docid Document id - * @param string $salt_data Salt data + * @param string $password Password to check + * @param string $docid Document id + * @param string $salt_data Salt data * @param string $hashedsalt_data Hashed salt data - * @param string $valContext Set to the MD5 context of the value + * @param string $valContext Set to the MD5 context of the value * * @return bool Success */ @@ -2974,7 +2974,7 @@ private function readSst(): void $pos = 0; // Limit position, further control for bad SST Length in BIFF8 data - $limitpos = 0; + $limitposSST = 0; // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); @@ -2994,14 +2994,12 @@ private function readSst(): void // it can happen that the string is empty, therefore we need // <= and not just < if ($pos <= $spliceOffset) { - $limitpos = $spliceOffset; - - break; + $limitposSST = $spliceOffset; } } // loop through the Unicode strings (16-bit length) - for ($i = 0; $i < $nm && $pos < $limitpos; ++$i) { + for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { // number of characters in the Unicode string $numChars = self::getUInt2d($recordData, $pos); $pos += 2; @@ -3099,7 +3097,7 @@ private function readSst(): void $len = min($charsLeft, $limitpos - $pos); for ($j = 0; $j < $len; ++$j) { $retstr .= $recordData[$pos + $j] - . chr(0); + . chr(0); } $charsLeft -= $len; $isCompressed = false; @@ -5317,7 +5315,8 @@ private function getSplicedRecordData() * Convert formula structure into human readable Excel formula like 'A3+A5*5'. * * @param string $formulaStructure The complete binary data for the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared + * formulas * * @return string Human readable formula */ @@ -5342,9 +5341,10 @@ private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1') /** * Take formula data and additional data for formula and return human readable formula. * - * @param string $formulaData The binary data for the formula itself + * @param string $formulaData The binary data for the formula itself * @param string $additionalData Additional binary data going with the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared + * formulas * * @return string Human readable formula */ @@ -5366,7 +5366,7 @@ private function getFormulaFromData($formulaData, $additionalData = '', $baseCel /** * Take array of tokens together with additional data for formula and return human readable formula. * - * @param array $tokens + * @param array $tokens * @param string $additionalData Additional binary data going with the formula * * @return string Human readable formula @@ -5542,7 +5542,7 @@ private function createFormulaFromTokens($tokens, $additionalData) * Fetch next token from binary formula data. * * @param string $formulaData Formula data - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return array */ @@ -7758,7 +7758,7 @@ private static function readUnicodeStringLong($subData) * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. * * @param string $subData - * @param int $characterCount + * @param int $characterCount * * @return array */ @@ -7865,7 +7865,7 @@ private static function getIEEE754($rknum) * Get UTF-8 string from (compressed or uncompressed) UTF-16 string. * * @param string $string - * @param bool $compressed + * @param bool $compressed * * @return string */ @@ -7912,7 +7912,7 @@ private function decodeCodepage($string) * Read 16-bit unsigned integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ @@ -7925,7 +7925,7 @@ public static function getUInt2d($data, $pos) * Read 16-bit signed integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ @@ -7938,7 +7938,7 @@ public static function getInt2d($data, $pos) * Read 32-bit signed integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/XlsTest.php index 9d14aee9d2..130374b359 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsTest.php @@ -56,6 +56,7 @@ public function testLoadXlsBug1592(): void $sheet = $spreadsheet->getActiveSheet(); $col = $sheet->getHighestColumn(); $row = $sheet->getHighestRow(); + $newspreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); $newsheet = $newspreadsheet->getActiveSheet(); $newcol = $newsheet->getHighestColumn(); @@ -66,7 +67,15 @@ public function testLoadXlsBug1592(): void self::assertEquals($sheet->getColumnDimensions(), $newsheet->getColumnDimensions()); self::assertEquals($col, $newcol); self::assertEquals($row, $newrow); - self::assertEquals($sheet->getCell('A1')->getFormattedValue(), $newsheet->getCell('A1')->getFormattedValue()); - self::assertEquals($sheet->getCell("$col$row")->getFormattedValue(), $newsheet->getCell("$col$row")->getFormattedValue()); + + $rowIterator = $sheet->getRowIterator(); + + foreach ($rowIterator as $row) { + foreach ($row->getCellIterator() as $cell) { + $valOld = $cell->getFormattedValue(); + $valNew = $newsheet->getCell($cell->getCoordinate())->getFormattedValue(); + self::assertEquals($valOld, $valNew); + } + } } } From 5c229719c21415560fe892e37877c2498cb8e469 Mon Sep 17 00:00:00 2001 From: Gianluca Giovinazzo Date: Fri, 21 Aug 2020 18:58:27 +0200 Subject: [PATCH 04/52] Fix SST global limit count and improved bug test --- src/PhpSpreadsheet/Reader/Xls.php | 48 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 5caa865a08..3d9f9ae387 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -1290,7 +1290,7 @@ public function load($pFilename) } } // Named Value - // TODO Provide support for named values + // TODO Provide support for named values } } $this->data = null; @@ -1302,8 +1302,8 @@ public function load($pFilename) * Read record data from stream, decrypting as required. * * @param string $data Data stream to read from - * @param int $pos Position to start reading from - * @param int $len Record data length + * @param int $pos Position to start reading from + * @param int $len Record data length * * @return string Record data */ @@ -1670,8 +1670,8 @@ private function readDefault(): void } /** - * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier - * versions, this record stores a note (cell note). This feature was significantly enhanced in Excel 97. + * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, + * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. */ private function readNote(): void { @@ -1718,7 +1718,7 @@ private function readNote(): void } else { // Set comment for the cell $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText)); - // ->setAuthor($author) +// ->setAuthor($author) } } } @@ -1850,7 +1850,7 @@ private function readFilepass(): void /** * Make an RC4 decryptor for the given block. * - * @param int $block Block for which to create decrypto + * @param int $block Block for which to create decrypto * @param string $valContext MD5 context state * * @return Xls\RC4 @@ -1882,11 +1882,11 @@ private function makeKey($block, $valContext) /** * Verify RC4 file password. * - * @param string $password Password to check - * @param string $docid Document id - * @param string $salt_data Salt data + * @param string $password Password to check + * @param string $docid Document id + * @param string $salt_data Salt data * @param string $hashedsalt_data Hashed salt data - * @param string $valContext Set to the MD5 context of the value + * @param string $valContext Set to the MD5 context of the value * * @return bool Success */ @@ -2973,7 +2973,7 @@ private function readSst(): void // offset within (spliced) record data $pos = 0; - // Limit position, further control for bad SST Length in BIFF8 data + // Limit global SST position, further control for bad SST Length in BIFF8 data $limitposSST = 0; // get spliced record data @@ -3097,7 +3097,7 @@ private function readSst(): void $len = min($charsLeft, $limitpos - $pos); for ($j = 0; $j < $len; ++$j) { $retstr .= $recordData[$pos + $j] - . chr(0); + . chr(0); } $charsLeft -= $len; $isCompressed = false; @@ -5315,8 +5315,7 @@ private function getSplicedRecordData() * Convert formula structure into human readable Excel formula like 'A3+A5*5'. * * @param string $formulaStructure The complete binary data for the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared - * formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return string Human readable formula */ @@ -5341,10 +5340,9 @@ private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1') /** * Take formula data and additional data for formula and return human readable formula. * - * @param string $formulaData The binary data for the formula itself + * @param string $formulaData The binary data for the formula itself * @param string $additionalData Additional binary data going with the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared - * formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return string Human readable formula */ @@ -5366,7 +5364,7 @@ private function getFormulaFromData($formulaData, $additionalData = '', $baseCel /** * Take array of tokens together with additional data for formula and return human readable formula. * - * @param array $tokens + * @param array $tokens * @param string $additionalData Additional binary data going with the formula * * @return string Human readable formula @@ -5542,7 +5540,7 @@ private function createFormulaFromTokens($tokens, $additionalData) * Fetch next token from binary formula data. * * @param string $formulaData Formula data - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * * @return array */ @@ -7758,7 +7756,7 @@ private static function readUnicodeStringLong($subData) * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. * * @param string $subData - * @param int $characterCount + * @param int $characterCount * * @return array */ @@ -7865,7 +7863,7 @@ private static function getIEEE754($rknum) * Get UTF-8 string from (compressed or uncompressed) UTF-16 string. * * @param string $string - * @param bool $compressed + * @param bool $compressed * * @return string */ @@ -7912,7 +7910,7 @@ private function decodeCodepage($string) * Read 16-bit unsigned integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ @@ -7925,7 +7923,7 @@ public static function getUInt2d($data, $pos) * Read 16-bit signed integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ @@ -7938,7 +7936,7 @@ public static function getInt2d($data, $pos) * Read 32-bit signed integer. * * @param string $data - * @param int $pos + * @param int $pos * * @return int */ From ae6c28599d6c76f9ee0fed7f93bce5c36d7e881d Mon Sep 17 00:00:00 2001 From: oleibman Date: Thu, 1 Oct 2020 02:24:53 -0700 Subject: [PATCH 05/52] Calculation/DateTime Failure With PHP8 (#1661) The following code generates an error with PHP8: if ($t1[1] > 29) { $t1[1] += 1900; Under the "right" conditions, PHP8 evaluates the condition as true when PHP8 is a non-numeric string and then generates an error trying to perform += on that non-numeric string. Adding a numeric test eliminates the problem. All unit tests involving this code now succeed with both PHP7 and PHP8. --- src/PhpSpreadsheet/Calculation/DateTime.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpSpreadsheet/Calculation/DateTime.php b/src/PhpSpreadsheet/Calculation/DateTime.php index bfce2a0250..4c2b108ad9 100644 --- a/src/PhpSpreadsheet/Calculation/DateTime.php +++ b/src/PhpSpreadsheet/Calculation/DateTime.php @@ -489,7 +489,7 @@ public static function DATEVALUE($dateValue = 1) if ($yearFound) { array_unshift($t1, 1); } else { - if ($t1[1] > 29) { + if (is_numeric($t1[1]) && $t1[1] > 29) { $t1[1] += 1900; array_unshift($t1, 1); } else { From 91cdec807709c3b1063b4a63a65ec13458d287ed Mon Sep 17 00:00:00 2001 From: oleibman Date: Thu, 1 Oct 2020 03:38:36 -0700 Subject: [PATCH 06/52] Reader/Gnumeric Failure with PHP8 (#1662) * Reader/Gnumeric Failure with PHP8 An explicit cast from SimpleXML to int is now needed with PHP8, where it was performed implicitly with PHP7. Unit tests run correctly for both PHP7 and PHP8 on corrected code. --- src/PhpSpreadsheet/Reader/Gnumeric.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index f4a4489544..dc921c1e44 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -543,7 +543,8 @@ public function loadIntoExisting(string $pFilename, Spreadsheet $spreadsheet): S $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); - $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow']); + + $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']); $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; $styleAttributes = $styleRegion->Style->attributes(); From 98b81f187b56f46b35fc42ced59e318160392e4e Mon Sep 17 00:00:00 2001 From: oleibman Date: Fri, 2 Oct 2020 01:52:28 -0700 Subject: [PATCH 07/52] ReverseSort bug, exposed but not caused by PHP8 (#1660) Some tests of ReferenceHelper functions columnReverseSort and cellReverseSort which passed with PHP7 fail with PHP8. Both functions use the following construction: return 1 - strcasecmp(whatever); The "1" seems very mysterious. I believe that the correct code should be: return -strcasecmp(whatever); It appears in particular that PHP7 strcasecmp was never returning a value of 1 for the tests in question, but PHP8 strcasecmp does so. With the corrected code, the tests pass in both PHP7 and PHP8. --- src/PhpSpreadsheet/ReferenceHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 9205b76ee6..13f7cf71b9 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -69,7 +69,7 @@ public static function columnSort($a, $b) */ public static function columnReverseSort($a, $b) { - return 1 - strcasecmp(strlen($a) . $a, strlen($b) . $b); + return -strcasecmp(strlen($a) . $a, strlen($b) . $b); } /** @@ -108,7 +108,7 @@ public static function cellReverseSort($a, $b) [$bc, $br] = sscanf($b, '%[A-Z]%d'); if ($ar === $br) { - return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); + return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); } return ($ar < $br) ? 1 : -1; From c000815d84084947f7593d51c397ddfcddff7331 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 2 Oct 2020 10:55:50 +0200 Subject: [PATCH 08/52] Drop $this->spreadSheet null check from Xlsx Writer (#1646) $this->spreadSheet cannot be null --- src/PhpSpreadsheet/Writer/Xlsx.php | 396 ++++++++++++++--------------- 1 file changed, 194 insertions(+), 202 deletions(-) diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 4cb102873e..d71541c805 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -178,238 +178,234 @@ public function getWriterPart($pPartName) */ public function save($pFilename): void { - if ($this->spreadSheet !== null) { - // garbage collect - $this->pathNames = []; - $this->spreadSheet->garbageCollect(); - - $this->openFileHandle($pFilename); - - $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); - Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); - $saveDateReturnType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - - // Create string lookup table - $this->stringTable = []; - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); - } + // garbage collect + $this->pathNames = []; + $this->spreadSheet->garbageCollect(); - // Create styles dictionaries - $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet)); - $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet)); - $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet)); - $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet)); - $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet)); - $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet)); - - // Create drawing dictionary - $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet)); - - $options = new Archive(); - $options->setEnableZip64(false); - $options->setOutputStream($this->fileHandle); - - $this->zip = new ZipStream(null, $options); - - // Add [Content_Types].xml to ZIP file - $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts)); - - //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) - if ($this->spreadSheet->hasMacros()) { - $macrosCode = $this->spreadSheet->getMacrosCode(); - if ($macrosCode !== null) { - // we have the code ? - $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin - if ($this->spreadSheet->hasMacrosCertificate()) { - //signed macros ? - // Yes : add the certificate file and the related rels file - $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate()); - $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet)); - } + $this->openFileHandle($pFilename); + + $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); + $saveDateReturnType = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + // Create string lookup table + $this->stringTable = []; + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); + } + + // Create styles dictionaries + $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet)); + $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet)); + $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet)); + $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet)); + $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet)); + $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet)); + + // Create drawing dictionary + $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet)); + + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + $this->zip = new ZipStream(null, $options); + + // Add [Content_Types].xml to ZIP file + $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts)); + + //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) + if ($this->spreadSheet->hasMacros()) { + $macrosCode = $this->spreadSheet->getMacrosCode(); + if ($macrosCode !== null) { + // we have the code ? + $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin + if ($this->spreadSheet->hasMacrosCertificate()) { + //signed macros ? + // Yes : add the certificate file and the related rels file + $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate()); + $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet)); } } - //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) - if ($this->spreadSheet->hasRibbon()) { - $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); - $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data')); - if ($this->spreadSheet->hasRibbonBinObjects()) { - $tmpRootPath = dirname($tmpRibbonTarget) . '/'; - $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write - foreach ($ribbonBinObjects as $aPath => $aContent) { - $this->addZipFile($tmpRootPath . $aPath, $aContent); - } - //the rels for files - $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet)); + } + //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) + if ($this->spreadSheet->hasRibbon()) { + $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); + $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data')); + if ($this->spreadSheet->hasRibbonBinObjects()) { + $tmpRootPath = dirname($tmpRibbonTarget) . '/'; + $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write + foreach ($ribbonBinObjects as $aPath => $aContent) { + $this->addZipFile($tmpRootPath . $aPath, $aContent); } + //the rels for files + $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet)); } + } - // Add relationships to ZIP file - $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet)); - $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet)); + // Add relationships to ZIP file + $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet)); + $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet)); - // Add document properties to ZIP file - $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet)); - $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet)); - $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet); - if ($customPropertiesPart !== null) { - $this->addZipFile('docProps/custom.xml', $customPropertiesPart); - } + // Add document properties to ZIP file + $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet)); + $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet)); + $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet); + if ($customPropertiesPart !== null) { + $this->addZipFile('docProps/custom.xml', $customPropertiesPart); + } + + // Add theme to ZIP file + $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet)); + + // Add string table to ZIP file + $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable)); + + // Add styles to ZIP file + $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet)); + + // Add workbook to ZIP file + $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas)); - // Add theme to ZIP file - $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet)); - - // Add string table to ZIP file - $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable)); - - // Add styles to ZIP file - $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet)); - - // Add workbook to ZIP file - $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas)); - - $chartCount = 0; - // Add worksheets - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts)); - if ($this->includeCharts) { - $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); - if (count($charts) > 0) { - foreach ($charts as $chart) { - $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas)); - ++$chartCount; - } + $chartCount = 0; + // Add worksheets + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts)); + if ($this->includeCharts) { + $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); + if (count($charts) > 0) { + foreach ($charts as $chart) { + $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas)); + ++$chartCount; } } } + } - $chartRef1 = 0; - // Add worksheet relationships (drawings, ...) - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - // Add relationships - $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); - - // Add unparsedLoadedData - $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); - $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { - $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); - } + $chartRef1 = 0; + // Add worksheet relationships (drawings, ...) + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + // Add relationships + $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); + + // Add unparsedLoadedData + $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); + $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { + $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); } - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { - $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); - } + } + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { + $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); } + } - $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); - $drawingCount = count($drawings); - if ($this->includeCharts) { - $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); - } + $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); + $drawingCount = count($drawings); + if ($this->includeCharts) { + $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); + } - // Add drawing and image relationship parts - if (($drawingCount > 0) || ($chartCount > 0)) { - // Drawing relationships - $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); + // Add drawing and image relationship parts + if (($drawingCount > 0) || ($chartCount > 0)) { + // Drawing relationships + $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); - // Drawings - $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); - } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { - // Drawings - $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); - } + // Drawings + $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); + } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { + // Drawings + $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); + } - // Add unparsed drawings - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { - $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); - if ($drawingFile !== false) { - $drawingFile = ltrim($drawingFile, '.'); - $this->addZipFile('xl' . $drawingFile, $drawingXml); - } + // Add unparsed drawings + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { + $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); + if ($drawingFile !== false) { + $drawingFile = ltrim($drawingFile, '.'); + $this->addZipFile('xl' . $drawingFile, $drawingXml); } } + } - // Add comment relationship parts - if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { - // VML Comments - $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i))); + // Add comment relationship parts + if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { + // VML Comments + $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i))); - // Comments - $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); - } + // Comments + $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); + } - // Add unparsed relationship parts - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { - $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']); - } + // Add unparsed relationship parts + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { + $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']); } + } - // Add header/footer relationship parts - if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { - // VML Drawings - $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i))); + // Add header/footer relationship parts + if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { + // VML Drawings + $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i))); - // VML Drawing relationships - $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i))); + // VML Drawing relationships + $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i))); - // Media - foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { - $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath())); - } + // Media + foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { + $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath())); } } + } - // Add media - for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { - if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { - $imageContents = null; - $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); - if (strpos($imagePath, 'zip://') !== false) { - $imagePath = substr($imagePath, 6); - $imagePathSplitted = explode('#', $imagePath); - - $imageZip = new ZipArchive(); - $imageZip->open($imagePathSplitted[0]); - $imageContents = $imageZip->getFromName($imagePathSplitted[1]); - $imageZip->close(); - unset($imageZip); - } else { - $imageContents = file_get_contents($imagePath); - } - - $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); - } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { - ob_start(); - call_user_func( - $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(), - $this->getDrawingHashTable()->getByIndex($i)->getImageResource() - ); - $imageContents = ob_get_contents(); - ob_end_clean(); - - $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); + // Add media + for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { + if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { + $imageContents = null; + $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); + if (strpos($imagePath, 'zip://') !== false) { + $imagePath = substr($imagePath, 6); + $imagePathSplitted = explode('#', $imagePath); + + $imageZip = new ZipArchive(); + $imageZip->open($imagePathSplitted[0]); + $imageContents = $imageZip->getFromName($imagePathSplitted[1]); + $imageZip->close(); + unset($imageZip); + } else { + $imageContents = file_get_contents($imagePath); } - } - - Functions::setReturnDateType($saveDateReturnType); - Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); - // Close file - try { - $this->zip->finish(); - } catch (OverflowException $e) { - throw new WriterException('Could not close resource.'); + $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); + } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { + ob_start(); + call_user_func( + $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(), + $this->getDrawingHashTable()->getByIndex($i)->getImageResource() + ); + $imageContents = ob_get_contents(); + ob_end_clean(); + + $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); } + } + + Functions::setReturnDateType($saveDateReturnType); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); - $this->maybeCloseFileHandle(); - } else { - throw new WriterException('PhpSpreadsheet object unassigned.'); + // Close file + try { + $this->zip->finish(); + } catch (OverflowException $e) { + throw new WriterException('Could not close resource.'); } + + $this->maybeCloseFileHandle(); } /** @@ -419,11 +415,7 @@ public function save($pFilename): void */ public function getSpreadsheet() { - if ($this->spreadSheet !== null) { - return $this->spreadSheet; - } - - throw new WriterException('No Spreadsheet object assigned.'); + return $this->spreadSheet; } /** From 8a9dbce0491220e28bd66aa1d7415ed00e59f163 Mon Sep 17 00:00:00 2001 From: Arman Hosseini Date: Fri, 2 Oct 2020 12:35:29 +0330 Subject: [PATCH 09/52] Update index.md (#1620) --- docs/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index c1a064595c..b1207d530b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,9 +2,8 @@ ![Logo](./assets/logo.svg) -PhpSpreadsheet is a library written in pure PHP and providing a set of -classes that allow you to read from and to write to different -spreadsheet file formats, like Excel and LibreOffice Calc. +PhpSpreadsheet is a library written in pure PHP and offers a set of classes that +allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc. ## File formats supported From 69c6135e284dba12648db3ba528a7344c7224732 Mon Sep 17 00:00:00 2001 From: oleibman Date: Sun, 4 Oct 2020 18:21:40 -0700 Subject: [PATCH 10/52] Bug setting Superscript/Subscript to false (#1567) If font style Superscript is set to true, Subscript is set to false. Likewise, setting Subscript to true sets Superscript to false. Both of these are working as they should. However, setting Superscript to false causes Subscript to be set to true, and setting Subscript to false causes Superscript to be set to true. I believe that is an error in both cases. This change fixes it. There seem to be no existing tests for Font styles. I added the tests necessary to validate this change. I will put adding more on my to-do list. --- src/PhpSpreadsheet/Style/Font.php | 22 ++++------ tests/PhpSpreadsheetTests/Style/FontTest.php | 42 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Style/FontTest.php diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index eee7df0448..a062a38f0f 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -357,21 +357,18 @@ public function getSuperscript() /** * Set Superscript. * - * @param bool $pValue - * * @return $this */ - public function setSuperscript($pValue) + public function setSuperscript(bool $pValue) { - if ($pValue == '') { - $pValue = false; - } if ($this->isSupervisor) { $styleArray = $this->getStyleArray(['superscript' => $pValue]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->superscript = $pValue; - $this->subscript = !$pValue; + if ($this->superscript) { + $this->subscript = false; + } } return $this; @@ -394,21 +391,18 @@ public function getSubscript() /** * Set Subscript. * - * @param bool $pValue - * * @return $this */ - public function setSubscript($pValue) + public function setSubscript(bool $pValue) { - if ($pValue == '') { - $pValue = false; - } if ($this->isSupervisor) { $styleArray = $this->getStyleArray(['subscript' => $pValue]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { $this->subscript = $pValue; - $this->superscript = !$pValue; + if ($this->subscript) { + $this->superscript = false; + } } return $this; diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php new file mode 100644 index 0000000000..c014a4b61d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Style/FontTest.php @@ -0,0 +1,42 @@ +getActiveSheet(); + $cell = $sheet->getCell('A1'); + $cell->setValue('Cell A1'); + $font = $cell->getStyle()->getFont(); + $font->setSuperscript(true); + $font->setSubscript(true); + self::assertFalse($font->getSuperscript(), 'Earlier set true loses'); + self::assertTrue($font->getSubscript(), 'Last set true wins'); + $font->setSubscript(true); + $font->setSuperscript(true); + self::assertTrue($font->getSuperscript(), 'Last set true wins'); + self::assertFalse($font->getSubscript(), 'Earlier set true loses'); + $font->setSuperscript(false); + $font->setSubscript(false); + self::assertFalse($font->getSuperscript(), 'False remains unchanged'); + self::assertFalse($font->getSubscript(), 'False remains unchanged'); + $font->setSubscript(false); + $font->setSuperscript(false); + self::assertFalse($font->getSuperscript(), 'False remains unchanged'); + self::assertFalse($font->getSubscript(), 'False remains unchanged'); + $font->setSubscript(true); + $font->setSuperscript(false); + self::assertFalse($font->getSuperscript(), 'False remains unchanged'); + self::assertTrue($font->getSubscript(), 'True remains unchanged'); + $font->setSubscript(false); + $font->setSuperscript(true); + self::assertTrue($font->getSuperscript()); + self::assertFalse($font->getSubscript(), 'False remains unchanged'); + } +} From a509ec40b7536c3005580b7b95e7c0b2312acd9f Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Mon, 5 Oct 2020 10:27:19 +0900 Subject: [PATCH 11/52] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e4e5b90a..f4d98d94e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Improve Coverage for ODS Reader [#1545](https://github.com/phpoffice/phpspreadsheet/pull/1545) - Named formula implementation, and improved handling of Defined Names generally [#1535](https://github.com/PHPOffice/PhpSpreadsheet/pull/1535) - - fix resolution of relative named range values in the calculation engine; previously all named range values had been treated as absolute. +- fix resolution of relative named range values in the calculation engine; previously all named range values had been treated as absolute. +- Drop $this->spreadSheet null check from Xlsx Writer [#1646](https://github.com/phpoffice/phpspreadsheet/pull/1646) ### Deprecated @@ -49,6 +50,10 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed - PrintArea causes exception [#1544](https://github.com/phpoffice/phpspreadsheet/pull/1544) +- Calculation/DateTime Failure With PHP8 [#1661](https://github.com/phpoffice/phpspreadsheet/pull/1661) +- Reader/Gnumeric Failure with PHP8 [#1662](https://github.com/phpoffice/phpspreadsheet/pull/1662) +- ReverseSort bug, exposed but not caused by PHP8 [#1660](https://github.com/phpoffice/phpspreadsheet/pull/1660) +- Bug setting Superscript/Subscript to false [#1567](https://github.com/phpoffice/phpspreadsheet/pull/1567) ## 1.14.1 - 2020-07-19 From 0b2c2e18196eaaf55023b1638d1b90da96426dd8 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Mon, 5 Oct 2020 10:27:27 +0900 Subject: [PATCH 12/52] Sync README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 893b3784b1..0fed4301c5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ [![License](https://poser.pugx.org/phpoffice/phpspreadsheet/license.png)](https://packagist.org/packages/phpoffice/phpspreadsheet) [![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PhpSpreadsheet) -PhpSpreadsheet is a library written in pure PHP and providing a set of classes that allow you to read from and to write to different spreadsheet file formats, like Excel and LibreOffice Calc. +PhpSpreadsheet is a library written in pure PHP and offers a set of classes that +allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc. ## Documentation From d87d8c89ef1deaad028e350edb092e4c92c29071 Mon Sep 17 00:00:00 2001 From: Roland Eigelsreiter Date: Thu, 8 Oct 2020 15:02:14 +0200 Subject: [PATCH 13/52] fixed php8 deprecation warning for libxml_disable_entity_loader() (#1625) * fixed php8 deprecation warning for libxml_disable_entity_loader() --- CHANGELOG.md | 3 ++- .../Reader/Security/XmlScanner.php | 4 +-- .../Reader/Security/XmlScannerTest.php | 25 +++++++++++++++---- tests/PhpSpreadsheetTests/SettingsTest.php | 22 ++++++++++++---- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d98d94e0..fd9e8fd4bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -560,6 +560,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570) - Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575) - Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176) +- Fixed PHP8 deprecation warning for libxml_disable_entity_loader() [#1625](https://github.com/phpoffice/phpspreadsheet/pull/1625) ### General @@ -581,4 +582,4 @@ For a comprehensive list of all class changes, and a semi-automated migration pa ## Previous versions of PHPExcel -The changelog for the project when it was called PHPExcel is [still available](./CHANGELOG.PHPExcel.md). +The changelog for the project when it was called PHPExcel is [still available](./CHANGELOG.PHPExcel.md). \ No newline at end of file diff --git a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php index 55bba632ff..a65797c156 100644 --- a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php +++ b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -63,7 +63,7 @@ public static function threadSafeLibxmlDisableEntityLoaderAvailability() private function disableEntityLoaderCheck(): void { - if (Settings::getLibXmlDisableEntityLoader()) { + if (Settings::getLibXmlDisableEntityLoader() && \PHP_VERSION_ID < 80000) { $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); if (self::$libxmlDisableEntityLoaderValue === null) { @@ -74,7 +74,7 @@ private function disableEntityLoaderCheck(): void public static function shutdown(): void { - if (self::$libxmlDisableEntityLoaderValue !== null) { + if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) { libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); self::$libxmlDisableEntityLoaderValue = null; } diff --git a/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php b/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php index 8f42af10cb..f98ff7e1e1 100644 --- a/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php @@ -12,7 +12,10 @@ class XmlScannerTest extends TestCase { protected function setUp(): void { - libxml_disable_entity_loader(false); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader(false); + } } /** @@ -24,13 +27,19 @@ protected function setUp(): void */ public function testValidXML($filename, $expectedResult, $libxmlDisableEntityLoader): void { - $oldDisableEntityLoaderState = libxml_disable_entity_loader($libxmlDisableEntityLoader); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + $oldDisableEntityLoaderState = libxml_disable_entity_loader($libxmlDisableEntityLoader); + } $reader = XmlScanner::getInstance(new \PhpOffice\PhpSpreadsheet\Reader\Xml()); $result = $reader->scanFile($filename); self::assertEquals($expectedResult, $result); - libxml_disable_entity_loader($oldDisableEntityLoaderState); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($oldDisableEntityLoaderState); + } } public function providerValidXML() @@ -56,13 +65,19 @@ public function testInvalidXML($filename, $libxmlDisableEntityLoader): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Reader\Exception::class); - libxml_disable_entity_loader($libxmlDisableEntityLoader); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($libxmlDisableEntityLoader); + } $reader = XmlScanner::getInstance(new \PhpOffice\PhpSpreadsheet\Reader\Xml()); $expectedResult = 'FAILURE: Should throw an Exception rather than return a value'; $result = $reader->scanFile($filename); self::assertEquals($expectedResult, $result); - self::assertEquals($libxmlDisableEntityLoader, libxml_disable_entity_loader()); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + self::assertEquals($libxmlDisableEntityLoader, libxml_disable_entity_loader()); + } } public function providerInvalidXML() diff --git a/tests/PhpSpreadsheetTests/SettingsTest.php b/tests/PhpSpreadsheetTests/SettingsTest.php index 4dff2d2594..92f3bb1f66 100644 --- a/tests/PhpSpreadsheetTests/SettingsTest.php +++ b/tests/PhpSpreadsheetTests/SettingsTest.php @@ -14,20 +14,29 @@ class SettingsTest extends TestCase protected function setUp(): void { - $this->prevValue = libxml_disable_entity_loader(); - libxml_disable_entity_loader(false); // Enable entity loader + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + $this->prevValue = libxml_disable_entity_loader(); + libxml_disable_entity_loader(false); // Enable entity loader + } } protected function tearDown(): void { - libxml_disable_entity_loader($this->prevValue); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($this->prevValue); + } } public function testGetXMLSettings(): void { $result = Settings::getLibXmlLoaderOptions(); self::assertTrue((bool) ((LIBXML_DTDLOAD | LIBXML_DTDATTR) & $result)); - self::assertFalse(libxml_disable_entity_loader()); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + self::assertFalse(libxml_disable_entity_loader()); + } } public function testSetXMLSettings(): void @@ -35,6 +44,9 @@ public function testSetXMLSettings(): void Settings::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_DTDVALID); $result = Settings::getLibXmlLoaderOptions(); self::assertTrue((bool) ((LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_DTDVALID) & $result)); - self::assertFalse(libxml_disable_entity_loader()); + // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default + if (\PHP_VERSION_ID < 80000) { + self::assertFalse(libxml_disable_entity_loader()); + } } } From ed8bf9b421982be7facd463fbe0fbc8d6fade6a0 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 17:55:06 +0200 Subject: [PATCH 14/52] Switch from using poser badges to shields.io because poser wasn't picking up the correct license --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0fed4301c5..0cef8326ef 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ [![Build Status](https://travis-ci.org/PHPOffice/PhpSpreadsheet.svg?branch=master)](https://travis-ci.org/PHPOffice/PhpSpreadsheet) [![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) -[![Total Downloads](https://poser.pugx.org/phpoffice/phpspreadsheet/downloads.png)](https://packagist.org/packages/phpoffice/phpspreadsheet) -[![Latest Stable Version](https://poser.pugx.org/phpoffice/phpspreadsheet/v/stable.png)](https://packagist.org/packages/phpoffice/phpspreadsheet) -[![License](https://poser.pugx.org/phpoffice/phpspreadsheet/license.png)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![Total Downloads](https://img.shields.io/packagist/dt/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![Latest Stable Version](https://img.shields.io/github/v/release/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![License](https://img.shields.io/github/license/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) [![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PhpSpreadsheet) PhpSpreadsheet is a library written in pure PHP and offers a set of classes that From 30dffc3c532c249b8ba1c84b6a649a7a7bec96ad Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sat, 10 Oct 2020 20:38:46 +0900 Subject: [PATCH 15/52] Introduce GitHub Actions The plan is to keep Travis for a short while, until we are confident that GitHub Actions work well enough for us. And after that we can remove Travis entirely. There is a bunch of duplicated things but it allows us to maximize parallelismt to have results as soon as possible. API documentation generation is still missing. --- .github/workflows/main.yml | 131 +++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..e9b07fe185 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,131 @@ +name: Build +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - '7.2' + - '7.3' + - '7.4' + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Test with phpunit + run: ./vendor/bin/phpunit + + php-cs-fixer: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP-CS-Fixer + run: ./vendor/bin/php-cs-fixer fix --diff --verbose --dry-run + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Coverage + run: | + ./vendor/bin/phpunit --coverage-clover coverage-clover.xml + curl -LO https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage-clover.xml From 80bdfea050c6c252c00a2215c7b66f71f7a87450 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sat, 10 Oct 2020 22:51:04 +0900 Subject: [PATCH 16/52] Annotate problems in code --- .github/workflows/main.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9b07fe185..dc10f6b086 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,13 @@ jobs: - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - name: Test with phpunit + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Test with PHPUnit run: ./vendor/bin/phpunit php-cs-fixer: @@ -50,6 +56,7 @@ jobs: with: php-version: 7.4 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + tools: cs2pr - name: Get composer cache directory id: composer-cache @@ -66,7 +73,7 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP-CS-Fixer - run: ./vendor/bin/php-cs-fixer fix --diff --verbose --dry-run + run: ./vendor/bin/php-cs-fixer fix --format=checkstyle | cs2pr phpcs: runs-on: ubuntu-latest @@ -79,6 +86,7 @@ jobs: with: php-version: 7.4 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + tools: cs2pr - name: Get composer cache directory id: composer-cache @@ -95,7 +103,7 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP_CodeSniffer - run: ./vendor/bin/phpcs + run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr coverage: runs-on: ubuntu-latest From c6e49795cb27c3ab1f32535bd9a7b734a372fd1c Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sun, 11 Oct 2020 12:05:28 +0900 Subject: [PATCH 17/52] Publish API docs via GitHub Actions --- .github/workflows/github-pages.yml | 29 +++++++++++++++++++++++++++++ .travis.yml | 13 ------------- 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/github-pages.yml diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml new file mode 100644 index 0000000000..3bdeea1d62 --- /dev/null +++ b/.github/workflows/github-pages.yml @@ -0,0 +1,29 @@ +name: GithHub Pages +on: + push: + tags: + - '*' + +jobs: + github-pages: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none # remove xdebug + + - name: Build API documentation + run: | + curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0-rc/phpDocumentor.phar + php phpDocumentor.phar --directory src/ --target docs/api + + - name: Deploy to GithHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/api diff --git a/.travis.yml b/.travis.yml index 77eed44148..6e29283ce6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,16 +36,3 @@ jobs: after_script: - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover tests/coverage-clover.xml - - - stage: API documentations - if: tag is present - php: 7.4 - before_script: - - curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0-rc/phpDocumentor.phar - script: - - php phpDocumentor.phar --directory src/ --target docs/api - deploy: - provider: pages - skip-cleanup: true - local-dir: docs/api - github-token: $GITHUB_TOKEN From 5799feebd7baa9a2cade5489320ffe661a5bade4 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 9 Aug 2020 14:12:17 +0200 Subject: [PATCH 18/52] Starting from scratch again: PHP8 nightly build included in test run, and it should pickup PHPUnit >= 9.3 --- .travis.yml | 1 + composer.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6e29283ce6..93c141ab2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.2 - 7.3 - 7.4 + - nightly cache: directories: diff --git a/composer.json b/composer.json index 00b6fe9e0b..5e966ed94a 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "jpgraph/jpgraph": "^4.0", "mpdf/mpdf": "^8.0", "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^8.5|^9.3", "squizlabs/php_codesniffer": "^3.5", "tecnickcom/tcpdf": "^6.3" }, From 1781e2436cef75ddcb77d8d71b28551185479d74 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 9 Aug 2020 14:26:44 +0200 Subject: [PATCH 19/52] xdebug isn't built into the nightly PHP, so we'll get an error if we try to remove it; nightly should also be allow failures untilany identified issues are resolved --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 93c141ab2d..c86b39fb49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,12 +14,15 @@ cache: before_script: # Deactivate xdebug - - phpenv config-rm xdebug.ini + - if [[ $TRAVIS_PHP_VERSION <> nightly ]]; then phpenv config-rm xdebug.ini; fi - composer install --ignore-platform-reqs script: - ./vendor/bin/phpunit --color=always --coverage-text +allow_failures: + - php: nightly + jobs: include: From 5921406a3734c88ca986e94f28a55efaaf0faf09 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 9 Aug 2020 14:33:13 +0200 Subject: [PATCH 20/52] <>/!= --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c86b39fb49..e590500d58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: before_script: # Deactivate xdebug - - if [[ $TRAVIS_PHP_VERSION <> nightly ]]; then phpenv config-rm xdebug.ini; fi + - if [[ $TRAVIS_PHP_VERSION != nightly ]]; then phpenv config-rm xdebug.ini; fi - composer install --ignore-platform-reqs script: From 887593dee092ed6fc5a1f4bbb4a083792b8d78e2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Sep 2020 11:02:07 +0200 Subject: [PATCH 21/52] Update to versions of complex and matrix libraries --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5e966ed94a..a122ec2c32 100644 --- a/composer.json +++ b/composer.json @@ -54,8 +54,8 @@ "ext-zip": "*", "ext-zlib": "*", "maennchen/zipstream-php": "^2.1", - "markbaker/complex": "^1.4", - "markbaker/matrix": "^1.2", + "markbaker/complex": "^1.5|^2.0", + "markbaker/matrix": "^1.2|^2.0", "psr/simple-cache": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" From 254dcdccba3a4c537ca63d64f95bfcfc8fd17665 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 1 Oct 2020 11:30:09 +0200 Subject: [PATCH 22/52] Update to versions of complex and matrix libraries --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a122ec2c32..09e6edf90a 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ ] }, "require": { - "php": "^7.2", + "php": "^7.2|^8.0", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", From 516b63c7e46eb69ecea15cabc7506412e2fc33b6 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 8 Oct 2020 02:56:13 +0200 Subject: [PATCH 23/52] Separate compoer.json for PHP8 to enforce phpunit ^9.3 --- .travis.yml | 1 + composer.php8.json | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 composer.php8.json diff --git a/.travis.yml b/.travis.yml index e590500d58..ce867afbf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ cache: before_script: # Deactivate xdebug - if [[ $TRAVIS_PHP_VERSION != nightly ]]; then phpenv config-rm xdebug.ini; fi + - if [[ $TRAVIS_PHP_VERSION == nightly ]]; then cp composer.php8.json composer.json; fi - composer install --ignore-platform-reqs script: diff --git a/composer.php8.json b/composer.php8.json new file mode 100644 index 0000000000..b7ba2813b7 --- /dev/null +++ b/composer.php8.json @@ -0,0 +1,89 @@ +{ + "name": "phpoffice/phpspreadsheet", + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"], + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "scripts": { + "check": [ + "php-cs-fixer fix --ansi --dry-run --diff", + "phpcs", + "phpunit --color=always" + ], + "fix": [ + "php-cs-fixer fix --ansi" + ], + "versions": [ + "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.2- -n" + ] + }, + "require": { + "php": "^7.2|^8.0", + "ext-ctype": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-SimpleXML": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^1.5|^2.0", + "markbaker/matrix": "^1.2|^2.0", + "psr/simple-cache": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "require-dev": { + "dompdf/dompdf": "^0.8.5", + "friendsofphp/php-cs-fixer": "^2.16", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.3" + }, + "suggest": { + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" + }, + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "autoload-dev": { + "psr-4": { + "PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests" + } + } +} From 948a6f03de2aef4e8a7022d11264576a6867a372 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 8 Oct 2020 03:00:53 +0200 Subject: [PATCH 24/52] We don't want the composer lock file for PHP8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ce867afbf6..3fd69614fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ cache: before_script: # Deactivate xdebug - if [[ $TRAVIS_PHP_VERSION != nightly ]]; then phpenv config-rm xdebug.ini; fi + - if [[ $TRAVIS_PHP_VERSION == nightly ]]; then rm composer.lock; fi - if [[ $TRAVIS_PHP_VERSION == nightly ]]; then cp composer.php8.json composer.json; fi - composer install --ignore-platform-reqs From f6816ae747b970f6fcbed5c294b8b29ed774746d Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 8 Oct 2020 05:51:06 +0200 Subject: [PATCH 25/52] Work with existing composer file; but force a fresh install to require new dependency versions --- .travis.yml | 1 - composer.php8.json | 89 ---------------------------------------------- 2 files changed, 90 deletions(-) delete mode 100644 composer.php8.json diff --git a/.travis.yml b/.travis.yml index 3fd69614fe..28a9af0d24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ before_script: # Deactivate xdebug - if [[ $TRAVIS_PHP_VERSION != nightly ]]; then phpenv config-rm xdebug.ini; fi - if [[ $TRAVIS_PHP_VERSION == nightly ]]; then rm composer.lock; fi - - if [[ $TRAVIS_PHP_VERSION == nightly ]]; then cp composer.php8.json composer.json; fi - composer install --ignore-platform-reqs script: diff --git a/composer.php8.json b/composer.php8.json deleted file mode 100644 index b7ba2813b7..0000000000 --- a/composer.php8.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "phpoffice/phpspreadsheet", - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"], - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", - "type": "library", - "license": "MIT", - "authors": [ - { - "name": "Maarten Balliauw", - "homepage": "https://blog.maartenballiauw.be" - }, - { - "name": "Mark Baker", - "homepage": "https://markbakeruk.net" - }, - { - "name": "Franck Lefevre", - "homepage": "https://rootslabs.net" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Adrien Crivelli" - } - ], - "scripts": { - "check": [ - "php-cs-fixer fix --ansi --dry-run --diff", - "phpcs", - "phpunit --color=always" - ], - "fix": [ - "php-cs-fixer fix --ansi" - ], - "versions": [ - "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.2- -n" - ] - }, - "require": { - "php": "^7.2|^8.0", - "ext-ctype": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-fileinfo": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-SimpleXML": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "maennchen/zipstream-php": "^2.1", - "markbaker/complex": "^1.5|^2.0", - "markbaker/matrix": "^1.2|^2.0", - "psr/simple-cache": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "require-dev": { - "dompdf/dompdf": "^0.8.5", - "friendsofphp/php-cs-fixer": "^2.16", - "jpgraph/jpgraph": "^4.0", - "mpdf/mpdf": "^8.0", - "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.5", - "tecnickcom/tcpdf": "^6.3" - }, - "suggest": { - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer", - "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" - }, - "autoload": { - "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" - } - }, - "autoload-dev": { - "psr-4": { - "PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests" - } - } -} From b8ebd3f9a9143b6c3dc524247c21e33e1bb2e0b7 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 8 Oct 2020 06:09:16 +0200 Subject: [PATCH 26/52] Allow failures on nightly --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 28a9af0d24..3aa274eb58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ php: - 7.2 - 7.3 - 7.4 - - nightly cache: directories: From 9a7d8c9ad77c4739698027c56620eb7db4c47700 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 8 Oct 2020 06:10:16 +0200 Subject: [PATCH 27/52] Allow failures on nightly fails if we remove it from the grid --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3aa274eb58..28a9af0d24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.2 - 7.3 - 7.4 + - nightly cache: directories: From 297381846e46df29d57d587503cdfe7566c5589b Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 12:58:33 +0200 Subject: [PATCH 28/52] Use ZipArchive methods as zip_* functions are deprecated in PHP8 --- .../Writer/Xlsx/UnparsedDataCloneTest.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php index d2afb4239c..2eeaef9d48 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php @@ -34,19 +34,20 @@ public function testLoadSaveXlsxWithUnparsedDataClone(): void $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->save($resultFilename); $dupname = 'Unable to open saved file'; - $zip = zip_open($resultFilename); - if (is_resource($zip)) { + + $zip = new \ZipArchive(); + if ($zip->open($resultFilename) !== false) { $names = []; $dupname = ''; - while ($zip_entry = zip_read($zip)) { - $zipname = zip_entry_name($zip_entry); - if (in_array($zipname, $names)) { - $dupname .= "$zipname,"; + for ($index = 0; $index < $zip->numFiles; ++$index) { + $filename = $zip->getNameIndex($index); + if (in_array($filename, $names)) { + $dupname .= "$filename,"; } else { - $names[] = $zipname; + $names[] = $filename; } } - zip_close($zip); + $zip->close(); } unlink($resultFilename); self::assertEquals('', $dupname); From 997ed88f99e53d57055c0f0adabbbe2a6665c0f2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 14:31:16 +0200 Subject: [PATCH 29/52] Remove tests that include tcPDF and DomPDF libraries when running against PHP8, because neither library is yet PHP8-ready --- samples/Basic/26_Utf8.php | 10 ++++---- samples/Pdf/21b_Pdf.php | 24 +++++++++++-------- .../Functional/StreamTest.php | 13 +++++++--- .../PhpSpreadsheetTests/Helper/SampleTest.php | 10 ++++++++ 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/samples/Basic/26_Utf8.php b/samples/Basic/26_Utf8.php index 52a64509b3..52953251c8 100644 --- a/samples/Basic/26_Utf8.php +++ b/samples/Basic/26_Utf8.php @@ -12,10 +12,12 @@ // at this point, we could do some manipulations with the template, but we skip this step $helper->write($spreadsheet, __FILE__, ['Xlsx', 'Xls', 'Html']); -// Export to PDF (.pdf) -$helper->log('Write to PDF format'); -IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class); -$helper->write($spreadsheet, __FILE__, ['Pdf']); +if (\PHP_VERSION_ID < 80000) { + // Export to PDF (.pdf) + $helper->log('Write to PDF format'); + IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class); + $helper->write($spreadsheet, __FILE__, ['Pdf']); +} // Remove first two rows with field headers before exporting to CSV $helper->log('Removing first two heading rows for CSV export'); diff --git a/samples/Pdf/21b_Pdf.php b/samples/Pdf/21b_Pdf.php index c67ff3d29d..6770b7c5e8 100644 --- a/samples/Pdf/21b_Pdf.php +++ b/samples/Pdf/21b_Pdf.php @@ -32,11 +32,13 @@ function replaceBody(string $html): string $helper->log('Set orientation to landscape'); $spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); -$helper->log('Write to Dompdf'); -$writer = new Dompdf($spreadsheet); -$filename = $helper->getFileName('21b_Pdf_dompdf.xlsx', 'pdf'); -$writer->setEditHtmlCallback('replaceBody'); -$writer->save($filename); +if (\PHP_VERSION_ID < 80000) { + $helper->log('Write to Dompdf'); + $writer = new Dompdf($spreadsheet); + $filename = $helper->getFileName('21b_Pdf_dompdf.xlsx', 'pdf'); + $writer->setEditHtmlCallback('replaceBody'); + $writer->save($filename); +} $helper->log('Write to Mpdf'); $writer = new Mpdf($spreadsheet); @@ -44,8 +46,10 @@ function replaceBody(string $html): string $writer->setEditHtmlCallback('replaceBody'); $writer->save($filename); -$helper->log('Write to Tcpdf'); -$writer = new Tcpdf($spreadsheet); -$filename = $helper->getFileName('21b_Pdf_tcpdf.xlsx', 'pdf'); -$writer->setEditHtmlCallback('replaceBody'); -$writer->save($filename); +if (\PHP_VERSION_ID < 80000) { + $helper->log('Write to Tcpdf'); + $writer = new Tcpdf($spreadsheet); + $filename = $helper->getFileName('21b_Pdf_tcpdf.xlsx', 'pdf'); + $writer->setEditHtmlCallback('replaceBody'); + $writer->save($filename); +} \ No newline at end of file diff --git a/tests/PhpSpreadsheetTests/Functional/StreamTest.php b/tests/PhpSpreadsheetTests/Functional/StreamTest.php index dcca6b8a07..3ca01fc4de 100644 --- a/tests/PhpSpreadsheetTests/Functional/StreamTest.php +++ b/tests/PhpSpreadsheetTests/Functional/StreamTest.php @@ -10,16 +10,23 @@ class StreamTest extends TestCase { public function providerFormats(): array { - return [ + $providerFormats = [ ['Xls'], ['Xlsx'], ['Ods'], ['Csv'], ['Html'], - ['Tcpdf'], - ['Dompdf'], ['Mpdf'], ]; + + if (\PHP_VERSION_ID < 80000) { + $providerFormats = array_merge( + $providerFormats, + [['Tcpdf'],['Dompdf']] + ); + } + + return $providerFormats; } /** diff --git a/tests/PhpSpreadsheetTests/Helper/SampleTest.php b/tests/PhpSpreadsheetTests/Helper/SampleTest.php index eb729dc9a9..369b205e08 100644 --- a/tests/PhpSpreadsheetTests/Helper/SampleTest.php +++ b/tests/PhpSpreadsheetTests/Helper/SampleTest.php @@ -31,6 +31,16 @@ public function providerSample() 'Chart/32_Chart_read_write_PDF.php', // Unfortunately JpGraph is not up to date for latest PHP and raise many warnings 'Chart/32_Chart_read_write_HTML.php', // idem ]; + // TCPDF and DomPDF libraries don't support PHP8 yet + if (\PHP_VERSION_ID >= 80000) { + $skipped = array_merge( + $skipped, + [ + 'Pdf/21_Pdf_Domdf.php', + 'Pdf/21_Pdf_TCPDF.php', + ] + ); + } // Unfortunately some tests are too long be ran with code-coverage // analysis on Travis, so we need to exclude them From 15e712a85d33cde199711efb91b255bc8f5a4056 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 14:52:01 +0200 Subject: [PATCH 30/52] Let's try to figure out why we're having an issue with a temp csv file --- src/PhpSpreadsheet/Reader/Csv.php | 1 + tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index db2b50b928..30b39a9b9f 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -522,6 +522,7 @@ public function canRead($pFilename) // Attempt to guess mimetype $type = mime_content_type($pFilename); + var_dump($type); $supportedTypes = [ 'text/csv', 'text/plain', diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php index d048183c37..d479935673 100644 --- a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php @@ -153,6 +153,7 @@ public function testNotRequiredEnclosure2(): void $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r/', '', $filedata); + var_dump($filedata); $reader = new CsvReader(); $reader->setDelimiter($delimiter); $reader->setEnclosure($enclosure); From 14c001831ec1070a3985b8806902cd0a16a7c038 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 14:57:45 +0200 Subject: [PATCH 31/52] Need to add `application/csv` to the list of valid CSV mime types --- src/PhpSpreadsheet/Reader/Csv.php | 2 +- tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index 30b39a9b9f..d6eb16b0af 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -522,8 +522,8 @@ public function canRead($pFilename) // Attempt to guess mimetype $type = mime_content_type($pFilename); - var_dump($type); $supportedTypes = [ + 'application/csv', 'text/csv', 'text/plain', 'inode/x-empty', diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php index d479935673..d048183c37 100644 --- a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php @@ -153,7 +153,6 @@ public function testNotRequiredEnclosure2(): void $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r/', '', $filedata); - var_dump($filedata); $reader = new CsvReader(); $reader->setDelimiter($delimiter); $reader->setEnclosure($enclosure); From 25ce4846933ccffc29794b8703aa8d9336b88ee2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 15:59:59 +0200 Subject: [PATCH 32/52] Locale-specific float to string is changed in PHP as per https://wiki.php.net/rfc/locale_independent_float_to_string --- tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php index 81d4a77736..83defb7b11 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php @@ -54,6 +54,7 @@ public function testLocaleFloatsCorrectlyConvertedByWriter(): void preg_match('/(?:double|float)\(([^\)]+)\)/mui', ob_get_clean(), $matches); self::assertArrayHasKey(1, $matches); $actual = $matches[1]; - self::assertEquals('1,1', $actual); + // From PHP8, https://wiki.php.net/rfc/locale_independent_float_to_string applies + self::assertEquals((\PHP_VERSION_ID < 80000) ? '1,1' : '1.1', $actual); } } From 892244e5cd4e0330913977c2be990ab239f94ca2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 9 Oct 2020 16:08:20 +0200 Subject: [PATCH 33/52] Codestyle clean-ups --- samples/Pdf/21b_Pdf.php | 2 +- tests/PhpSpreadsheetTests/Functional/StreamTest.php | 2 +- .../PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/Pdf/21b_Pdf.php b/samples/Pdf/21b_Pdf.php index 6770b7c5e8..ad2f609b7c 100644 --- a/samples/Pdf/21b_Pdf.php +++ b/samples/Pdf/21b_Pdf.php @@ -52,4 +52,4 @@ function replaceBody(string $html): string $filename = $helper->getFileName('21b_Pdf_tcpdf.xlsx', 'pdf'); $writer->setEditHtmlCallback('replaceBody'); $writer->save($filename); -} \ No newline at end of file +} diff --git a/tests/PhpSpreadsheetTests/Functional/StreamTest.php b/tests/PhpSpreadsheetTests/Functional/StreamTest.php index 3ca01fc4de..03f9a2d92a 100644 --- a/tests/PhpSpreadsheetTests/Functional/StreamTest.php +++ b/tests/PhpSpreadsheetTests/Functional/StreamTest.php @@ -22,7 +22,7 @@ public function providerFormats(): array if (\PHP_VERSION_ID < 80000) { $providerFormats = array_merge( $providerFormats, - [['Tcpdf'],['Dompdf']] + [['Tcpdf'], ['Dompdf']] ); } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php index 2eeaef9d48..660e40feff 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php @@ -5,6 +5,7 @@ use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\File; use PHPUnit\Framework\TestCase; +use ZipArchive; class UnparsedDataCloneTest extends TestCase { @@ -35,7 +36,7 @@ public function testLoadSaveXlsxWithUnparsedDataClone(): void $writer->save($resultFilename); $dupname = 'Unable to open saved file'; - $zip = new \ZipArchive(); + $zip = new ZipArchive(); if ($zip->open($resultFilename) !== false) { $names = []; $dupname = ''; From 82143bef0cb5974480eb6f962c6c0f3dfdbfe8f3 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 10 Oct 2020 12:11:00 +0200 Subject: [PATCH 34/52] Update composer suggestions --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 09e6edf90a..0fcb2556dd 100644 --- a/composer.json +++ b/composer.json @@ -72,8 +72,8 @@ }, "suggest": { "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer", + "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" }, "autoload": { From 7791931cd3a14eacd72f6c096e6deb8ace6fcd60 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 11 Oct 2020 12:36:47 +0200 Subject: [PATCH 35/52] Update composer suggestions --- tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php index 83defb7b11..373ef37f31 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php @@ -55,6 +55,6 @@ public function testLocaleFloatsCorrectlyConvertedByWriter(): void self::assertArrayHasKey(1, $matches); $actual = $matches[1]; // From PHP8, https://wiki.php.net/rfc/locale_independent_float_to_string applies - self::assertEquals((\PHP_VERSION_ID < 80000) ? '1,1' : '1.1', $actual); + self::assertEquals('1,1', $actual); } } From eb1c64ee84c2df7a951a04f1a5db46c754a403a5 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sun, 11 Oct 2020 19:42:25 +0900 Subject: [PATCH 36/52] Test PHP 8 on GitHub Actions --- .github/workflows/main.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc10f6b086..88168fa007 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,6 +9,7 @@ jobs: - '7.2' - '7.3' - '7.4' + - '8.0' name: PHP ${{ matrix.php-version }} @@ -20,7 +21,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none - name: Get composer cache directory id: composer-cache @@ -33,8 +35,15 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- + - name: Delete composer lock file + id: composer-lock + if: ${{ matrix.php-version == '8.0' }} + run: | + rm composer.lock + echo "::set-output name=flags::--ignore-platform-reqs" + - name: Install dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader + run: composer install --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" @@ -55,7 +64,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: 7.4 - extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none tools: cs2pr - name: Get composer cache directory @@ -85,7 +95,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: 7.4 - extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none tools: cs2pr - name: Get composer cache directory @@ -115,7 +126,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: 7.4 - extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, SimpleXML, xml, xmlreader, xmlwriter, zip, zlib + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: pcov - name: Get composer cache directory From fb949f7874d4317bcfb3c1c3ed3df99678af09ac Mon Sep 17 00:00:00 2001 From: oleibman Date: Sun, 11 Oct 2020 04:26:56 -0700 Subject: [PATCH 37/52] Improving Coverage for Excel2003 XML Reader (#1557) * Improving Coverage for Excel2003 XML Reader Reader/Xml is now 100% covered. File templates/Excel2003XMLTest.xml, used in some tests, is *not* readable by a current version of Excel. I have substituted a new file excel2003.xml to be used in its place. I have not deleted the original in case someone in future (possibly me) wants to see what it needs to make it usable. There are minimal code changes. - Unused protected functions pixel2WidthUnits and widthUnits2Pixel are deleted. - One regex looking to convert hex characters is changed from a-z to a-f, and made case insensitive. - No calculation performed for "error" cell (previously calculation was attempted and threw exception). - Empty relative row/cell is now handled correctly. - Style applied to empty cell when appropriate. - Support added for textRotation. - Support added for border styles. - Support added for diagonal borders. - Support added for superscript and subscript. - Support added for fill patterns. In theory, encodings other than UTF-8 were supported. In fact, I was unable to get SecurityScanner to pass *any* xml which is not UTF-8. Eliminating the assumption that strings might not be UTF-8 allowed much of the code to be greatly simplified. After that, I added some code that would permit the use of some ASCII-compatible encodings (there is a test of ISO-8859-1). It would be more difficult to handle other encodings (such as UTF-16). I am not convinced that even the ISO-8859 effort is worth it, but am willing to investigate either expanding or eliminating non-UTF8 support. I added a number of tests, creating an Xml directory, and moving XmlTest to that directory. Pull Request had problems reading old invalid sample in the code coverage phase, not in any of the other test phases, and not in the code coverage phase on my local machine. As it turns out, aside from being invalid, the sample is much larger than any of the other samples. Tests have been adjusted accordingly. * Smaller Test File Should eliminate need to avoid test during xml coverage. * Break Up Style Test into Multiple Tests Per suggestion from Mark Baker. * Integrate AddressHelper Change The introduction of AddressHelper introduced a conflict which needed to be resolved. I wanted to test it locally before resolving. This required me to add (unchanged) AddressHelper to my local copy. I hope this is an okay manner of resolving the conflict. * Weird Travis Error XmlOddTest works just fine on my local machine, but Travis failed it. Even worse, the lines which Travis flags don't even make any sense (one was the empty line between two methods!). This test is not essential to the rest of the change. I am removing it from the package, and will attempt to re-add it when I have a chance to sync up my fork with the main project. --- samples/Basic/20_Read_Excel2003XML.php | 2 +- .../templates/excel2003.short.bad.xml | 26 +- samples/templates/excel2003.xml | 944 ++++++++++++++++++ src/PhpSpreadsheet/Reader/Xml.php | 275 ++--- tests/PhpSpreadsheetTests/IOFactoryTest.php | 4 +- tests/PhpSpreadsheetTests/Reader/CsvTest.php | 2 +- .../Reader/Xml/XmlFilter.php | 14 + .../Reader/Xml/XmlInfoTest.php | 75 ++ .../Reader/Xml/XmlLoadTest.php | 100 ++ .../Reader/Xml/XmlStyleCoverageTest.php | 113 +++ .../Reader/Xml/XmlStylesTest.php | 135 +++ .../Reader/{ => Xml}/XmlTest.php | 16 +- tests/data/Reader/Xml/excel2003.iso8859-1.xml | 67 ++ 13 files changed, 1645 insertions(+), 128 deletions(-) rename tests/data/Reader/Xml/WithoutStyle.xml => samples/templates/excel2003.short.bad.xml (66%) create mode 100644 samples/templates/excel2003.xml create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/XmlFilter.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/XmlInfoTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/XmlStyleCoverageTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/XmlStylesTest.php rename tests/PhpSpreadsheetTests/Reader/{ => Xml}/XmlTest.php (70%) create mode 100644 tests/data/Reader/Xml/excel2003.iso8859-1.xml diff --git a/samples/Basic/20_Read_Excel2003XML.php b/samples/Basic/20_Read_Excel2003XML.php index 44425e20a5..48ac3373c5 100644 --- a/samples/Basic/20_Read_Excel2003XML.php +++ b/samples/Basic/20_Read_Excel2003XML.php @@ -4,7 +4,7 @@ require __DIR__ . '/../Header.php'; -$filename = __DIR__ . '/../templates/Excel2003XMLTest.xml'; +$filename = __DIR__ . '/../templates/excel2003.xml'; $callStartTime = microtime(true); $spreadsheet = IOFactory::load($filename); $helper->logRead('Xml', $filename, $callStartTime); diff --git a/tests/data/Reader/Xml/WithoutStyle.xml b/samples/templates/excel2003.short.bad.xml similarity index 66% rename from tests/data/Reader/Xml/WithoutStyle.xml rename to samples/templates/excel2003.short.bad.xml index b8698b0497..bd9674bc3a 100644 --- a/tests/data/Reader/Xml/WithoutStyle.xml +++ b/samples/templates/excel2003.short.bad.xml @@ -20,6 +20,19 @@ False False + + + + @@ -28,7 +41,7 @@ - + Test String 1 @@ -38,7 +51,16 @@ - 1 + 1 + + + 12 + + + 11 + + + 1960-12-19T00:00:00.000
diff --git a/samples/templates/excel2003.xml b/samples/templates/excel2003.xml new file mode 100644 index 0000000000..cd1188a72b --- /dev/null +++ b/samples/templates/excel2003.xml @@ -0,0 +1,944 @@ + + + + + Xml2003 Workbook + Test Gnumeric Workbook Subject + Mark Baker + PHPExcel Xml Reader Test Keywords + Some comments about the PHPExcel Gnumeric Reader + Owen Leibman + 2010-09-02T20:48:39Z + 2010-09-02T20:48:39Z + PHPExcel Xml Reader Test Category + Maarten Balliauw + PHPExcel + https://github.com/PHPOffice/PhpSpreadsheet + 16.00 + + + AbCd1234 + 2019-01-31T07:00:00Z + 1 + 3 + 2 + 3.14159 + + + + + + 8964 + 23040 + 32767 + 32767 + False + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test String 1Test for a simple colour-formatted string + 1 + 5 + A + E + 6 + AE + + + Test - String 2 + 2 + 6 + B + F + 8 + BF + Dot + + + Test #3 + 3 + 7 + C + G + 10 + CG + Red + Red + Dash + + + Test with (") in string + 4 + 8 + D + H + 12 + DH + Orange + Orange + Dash/Dot/Dot + + + 10 + 26 + 36 + Yellow + Yellow + Dash/Dot + + + Test #3 + 1.23 + 1 + 1 + 22 + Green + Green + Thin Line + + + Test #3 + 2.34 + 0 + 0 + 36 + Blue + Blue + Thick Dash/Dot/Dot + + + Test #3 + 3.45 + Purple + Purple + Variant Thick Dash/Dot/Dot + + + Pink + Pink + Thick Dash/Dot + + + 1960-12-19T00:00:00.000 + TOP + 0 + Brown + Brown + Thick Dash + + + 1.5 + #DIV/0! + Thick Line + + + BOTTOM + Extra Thick Line + + + 1899-12-31T02:30:00.000 + Мойва сушенаяTests for UTF-8 content + Double Line + + + LEFT + Ärendetext + + + 1960-12-19T01:30:00.000 + Højde + + + RIGHT + + + + BOX + + Test Column 1 + + + + + + + Test Column 2 + + Patterned + Patterned 2 + + + + + Test Column 3 + + + PhpSpreadsheet + + + + + + + + + + + + + + + + Underline None + + Rotate 90 + Rotate 45 + Rotate -90 + Rotate -45 + + + + + + + Underline 1 + Subscript + + + + + + + Underline 2 + Superscript + + + + + + + Underline 3 + + + + + + + + Underline 4 + + + + + + + + + + + + + + + + + + + + + + + + I don't know if Gnumeric supports Rich Text in the same way as Excel, And this row should be autofit height with text wrap + + + + + + + + + + + + + + Blue with underline + + + + + + + + + + + + + + 5 + 5 + #NAME? + + + + + + + + + + + + + + Hidden row above + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+