2626use PhpOffice \PhpSpreadsheet \Shared \StringHelper ;
2727use PhpOffice \PhpSpreadsheet \Spreadsheet ;
2828use PhpOffice \PhpSpreadsheet \Style \Alignment ;
29+ use PhpOffice \PhpSpreadsheet \Style \Border ;
30+ use PhpOffice \PhpSpreadsheet \Style \Borders ;
2931use PhpOffice \PhpSpreadsheet \Style \Fill ;
3032use PhpOffice \PhpSpreadsheet \Style \NumberFormat ;
3133use PhpOffice \PhpSpreadsheet \Style \Protection ;
@@ -284,6 +286,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
284286 * locked?: string,
285287 * hidden?: string,
286288 * },
289+ * borders?:array{
290+ * bottom?: array{borderStyle:string, color:array{rgb: string}},
291+ * left?: array{borderStyle:string, color:array{rgb: string}},
292+ * right?: array{borderStyle:string, color:array{rgb: string}},
293+ * top?: array{borderStyle:string, color:array{rgb: string}},
294+ * diagonal?: array{borderStyle:string, color:array{rgb: string}},
295+ * diagonalDirection?: int,
296+ * },
287297 * }>
288298 */
289299 private array $ allStyles ;
@@ -409,12 +419,13 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
409419 }
410420 }
411421 if ($ styleFamily === 'table-cell ' ) {
412- $ fonts = $ fills = $ alignment1 = $ alignment2 = $ protection = [];
422+ $ fonts = $ fills = $ alignment1 = $ alignment2 = $ protection = $ borders = [];
413423 foreach ($ automaticStyle ->getElementsByTagNameNS ($ styleNs , 'text-properties ' ) as $ textProperty ) {
414424 $ fonts = $ this ->getFontStyles ($ textProperty , $ styleNs , $ fontNs );
415425 }
416426 foreach ($ automaticStyle ->getElementsByTagNameNS ($ styleNs , 'table-cell-properties ' ) as $ tableCellProperty ) {
417427 $ fills = $ this ->getFillStyles ($ tableCellProperty , $ fontNs );
428+ $ borders = $ this ->getBorderStyles ($ tableCellProperty , $ fontNs , $ styleNs );
418429 $ protection = $ this ->getProtectionStyles ($ tableCellProperty , $ styleNs );
419430 }
420431 foreach ($ automaticStyle ->getElementsByTagNameNS ($ styleNs , 'table-cell-properties ' ) as $ tableCellProperty ) {
@@ -437,6 +448,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
437448 if (!empty ($ protection )) {
438449 $ this ->allStyles [$ styleName ]['protection ' ] = $ protection ;
439450 }
451+ if (!empty ($ borders )) {
452+ $ this ->allStyles [$ styleName ]['borders ' ] = $ borders ;
453+ }
440454 }
441455 }
442456 }
@@ -746,9 +760,34 @@ private function processTableRow(
746760 }
747761 // Fall through to process the cell, with per-column filter checks
748762 }
749- if ($ worksheet !== null && $ cellData ->hasChildNodes () && isset ($ this ->allStyles [$ styleName ])) {
750- $ worksheet ->getStyle ("$ columnID$ rowID " )
763+ if ($ worksheet !== null && ($ cellData ->hasChildNodes () || ($ cellData ->nextSibling !== null )) && isset ($ this ->allStyles [$ styleName ])) {
764+ $ spannedRange = "$ columnID$ rowID " ;
765+ // the following is sufficient for ods,
766+ // and does no harm for xlsx/xls.
767+ $ worksheet ->getStyle ($ spannedRange )
751768 ->applyFromArray ($ this ->allStyles [$ styleName ]);
769+ // the rest of this block is needed for xlsx/xls,
770+ // and does no harm for ods.
771+ if (isset ($ this ->allStyles [$ styleName ]['borders ' ])) {
772+ $ spannedRows = $ cellData ->getAttributeNS ($ tableNs , 'number-columns-spanned ' );
773+ $ spannedColumns = $ cellData ->getAttributeNS ($ tableNs , 'number-rows-spanned ' );
774+ $ spannedRows = max ((int ) $ spannedRows , 1 );
775+ $ spannedColumns = max ((int ) $ spannedColumns , 1 );
776+ if ($ spannedRows > 1 || $ spannedColumns > 1 ) {
777+ $ endRow = $ rowID + $ spannedRows - 1 ;
778+ $ endCol = $ columnID ;
779+ while ($ spannedColumns > 1 ) {
780+ StringHelper::stringIncrement ($ endCol );
781+ --$ spannedColumns ;
782+ }
783+ $ spannedRange .= ": $ endCol$ endRow " ;
784+ $ worksheet ->getStyle ($ spannedRange )
785+ ->getBorders ()
786+ ->applyFromArray (
787+ $ this ->allStyles [$ styleName ]['borders ' ]
788+ );
789+ }
790+ }
752791 }
753792
754793 // Initialize variables
@@ -1508,4 +1547,74 @@ protected function getProtectionStyles(DOMElement $tableCellProperties, string $
15081547
15091548 return $ protection ;
15101549 }
1550+
1551+ private const MAP_BORDER_STYLE = [ // default BORDER_THIN
1552+ 'none ' => Border::BORDER_NONE ,
1553+ 'hidden ' => Border::BORDER_NONE ,
1554+ 'dotted ' => Border::BORDER_DOTTED ,
1555+ 'dash-dot ' => Border::BORDER_DASHDOT ,
1556+ 'dash-dot-dot ' => Border::BORDER_DASHDOTDOT ,
1557+ 'dashed ' => Border::BORDER_DASHED ,
1558+ 'double ' => Border::BORDER_DOUBLE ,
1559+ ];
1560+
1561+ private const MAP_BORDER_MEDIUM = [
1562+ Border::BORDER_THIN => Border::BORDER_MEDIUM ,
1563+ Border::BORDER_DASHDOT => Border::BORDER_MEDIUMDASHDOT ,
1564+ Border::BORDER_DASHDOTDOT => Border::BORDER_MEDIUMDASHDOTDOT ,
1565+ Border::BORDER_DASHED => Border::BORDER_MEDIUMDASHED ,
1566+ ];
1567+
1568+ private const MAP_BORDER_THICK = [
1569+ Border::BORDER_THIN => Border::BORDER_THICK ,
1570+ Border::BORDER_DASHDOT => Border::BORDER_MEDIUMDASHDOT ,
1571+ Border::BORDER_DASHDOTDOT => Border::BORDER_MEDIUMDASHDOTDOT ,
1572+ Border::BORDER_DASHED => Border::BORDER_MEDIUMDASHED ,
1573+ ];
1574+
1575+ /** @return array{
1576+ * bottom?: array{borderStyle:string, color:array{rgb: string}},
1577+ * top?: array{borderStyle:string, color:array{rgb: string}},
1578+ * left?: array{borderStyle:string, color:array{rgb: string}},
1579+ * right?: array{borderStyle:string, color:array{rgb: string}},
1580+ * diagonal?: array{borderStyle:string, color:array{rgb: string}},
1581+ * diagonalDirection?: int,
1582+ * }
1583+ */
1584+ protected function getBorderStyles (DOMElement $ tableCellProperties , string $ fontNs , string $ styleNs ): array
1585+ {
1586+ $ borders = [];
1587+ $ temp = $ tableCellProperties ->getAttributeNs ($ fontNs , 'border ' );
1588+ $ diagonalIndex = Borders::DIAGONAL_NONE ;
1589+ foreach (['bottom ' , 'left ' , 'right ' , 'top ' , 'diagonal-tl-br ' , 'diagonal-bl-tr ' ] as $ direction ) {
1590+ if (str_starts_with ($ direction , 'diagonal ' )) {
1591+ $ directionIndex = 'diagonal ' ;
1592+ $ temp = $ tableCellProperties ->getAttributeNs ($ styleNs , $ direction );
1593+ } else {
1594+ $ directionIndex = $ direction ;
1595+ $ temp = $ tableCellProperties ->getAttributeNs ($ fontNs , "border- $ direction " );
1596+ }
1597+ if (Preg::isMatch ('/^(\d+(?:[.]\d+)?)pt\s+([-\w]+)\s+#([0-9a-fA-F]{6})$/ ' , $ temp , $ matches )) {
1598+ $ style = self ::MAP_BORDER_STYLE [$ matches [2 ]] ?? Border::BORDER_THIN ;
1599+ $ width = (float ) $ matches [1 ];
1600+ if ($ width >= 2.5 ) {
1601+ $ style = self ::MAP_BORDER_THICK [$ style ] ?? $ style ;
1602+ } elseif ($ width >= 1.75 ) {
1603+ $ style = self ::MAP_BORDER_MEDIUM [$ style ] ?? $ style ;
1604+ }
1605+ $ color = $ matches [3 ];
1606+ $ borders [$ directionIndex ] = ['borderStyle ' => $ style , 'color ' => ['rgb ' => $ matches [3 ]]];
1607+ if ($ direction === 'diagonal-tl-br ' ) {
1608+ $ diagonalIndex = Borders::DIAGONAL_DOWN ;
1609+ } elseif ($ direction === 'diagonal-bl-tr ' ) {
1610+ $ diagonalIndex = ($ diagonalIndex === Borders::DIAGONAL_NONE ) ? Borders::DIAGONAL_UP : Borders::DIAGONAL_BOTH ;
1611+ }
1612+ }
1613+ }
1614+ if ($ diagonalIndex !== Borders::DIAGONAL_NONE ) {
1615+ $ borders ['diagonalDirection ' ] = $ diagonalIndex ;
1616+ }
1617+
1618+ return $ borders ; // @phpstan-ignore-line
1619+ }
15111620}
0 commit comments