PHPWord
PHPWord copied to clipboard
Is there any support for rowspan and colspan in addHTML ?
We are not able to add Html tables with rowspan using Html::addHtml().
After generating content into docx Table Rows and collspans are not rendering properly .
Please find the below screen shot that actually breaking the table.

Please find the attached below screen shot for Original content.

Any help on this!
Thanks, Ramesh S
Any news? I'm having the same issue.
It looks like colspan is recognized by the HTML reader, but rowspan is not. I took a quick look, and it's a little trickier mapping rowspan to PHPWord because HTML uses a number (like colspan), but PHPWord has a flag on cells to mark whether it's part of a rowspan or not. It requires a bit more state tracking between rows, so it appears it was skipped. I'm waiting on #1669 to merge, but then there's a good chance I'll be able to tackle a fix for this.
Any news regarding rowspan support ? Would be worth value. Thanks
please add this code at phpoffice/phpword/src/PhpWord/Shared/Html.php
protected static function parseCell($node, $element, &$styles)
{
$cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
$colspan = $node->getAttribute('colspan');
if (!empty($colspan)) {
$cellStyles['gridSpan'] = $colspan - 0;
}
/** NEW **/
$rowspan = $node->getAttribute('rowspan');
if (!empty($rowspan)) {
$cellStyles['vMerge'] = 'restart';
}
$beforespan = $node->getAttribute('beforespan');
if (!empty($beforespan)) {
$cellRowContinue = array('vMerge' => 'continue');
$beforecolspan = $node->getAttribute('beforecolspan');
if( ! empty($beforecolspan) ) $cellRowContinue['gridSpan'] = $beforecolspan;
for($s=1;$s<=$beforespan;$s++){
$element->addCell(null,$cellRowContinue);
}
}
/*** END **/
// set cell width to control column widths
$width = isset($cellStyles['width']) ? $cellStyles['width'] : null;
unset($cellStyles['width']); // would not apply
$cell = $element->addCell($width, $cellStyles);
if (self::shouldAddTextRun($node)) {
return $cell->addTextRun(self::filterOutNonInheritedStyles(self::parseInlineStyle($node, $styles['paragraph'])));
}
return $cell;
}
Code HTML
$html = '<table style="width: 100%; border: 1px #000000 solid;" cellspacing="0" collpadding="0">
<thead>
<tr style="background-color: #FF0000; text-align: center; color: #FFFFFF; font-weight: bold; ">
<th style="text-align:center;">A</th>
<th style="text-align:center;">B</th>
<th style="text-align:center;">C</th>
</tr>
</thead>
<tbody>
<tr><td > A1 </td><td colspan="2"> BC1 </td></tr>
<tr><td rowspan="2" colspan="2"> AB23 </td><td> C2 </td></tr>
<tr><td beforespan="1" beforecolspan="2" > C3 </td></tr>
<tr><td rowspan="3" > A456 </td><td> B4 </td><td> C4 </td></tr>
<tr><td rowspan="2" beforespan="1">B5</td><td>C5</td></tr>
<tr><td beforespan="2">C6</td></tr>
<tr><td> A7 </td><td> B7 </td><td> C7 </td></tr>
</tbody>
</table>';
$Section = new \PhpOffice\PhpWord\Element\Section(0);
\PhpOffice\PhpWord\Shared\Html::addHtml($Section, $html);
$document->setComplexBlock('tabel2',$Section->getElement(0)) ;
Output:

Hi @yherus : thanks for your proposal. I will try that soon.
And what about this ? https://github.com/PHPOffice/PHPWord/pull/2163
Thanks again
this is just a trick for my project, update: phpoffice/phpword/src/PhpWord/Shared/Html.php
protected static function parseCell($node, $element, &$styles)
{
$cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
$colspan = $node->getAttribute('colspan');
if (!empty($colspan)) {
$cellStyles['gridSpan'] = $colspan - 0;
}
$rowspan = $node->getAttribute('rowspan');
if (!empty($rowspan)) {
$cellStyles['vMerge'] = 'restart';
}
$beforespan = $node->getAttribute('beforespan');
if (!empty($beforespan)) {
$cellRowContinue = array('vMerge' => 'continue');
$beforecolspan = $node->getAttribute('beforecolspan');
if( ! empty($beforecolspan) ) $cellRowContinue['gridSpan'] = $beforecolspan;
for($s=1;$s<=$beforespan;$s++){
$element->addCell(null,$cellRowContinue);
}
}
// set cell width to control column widths
$width = isset($cellStyles['width']) ? $cellStyles['width'] : null;
unset($cellStyles['width']); // would not apply
$cell = $element->addCell($width, $cellStyles);
$afterspan = $node->getAttribute('afterspan');
if (!empty($afterspan)) {
$cellRowContinue = array('vMerge' => 'continue');
$aftercolspan = $node->getAttribute('aftercolspan');
if( ! empty($aftercolspan) ) $cellRowContinue['gridSpan'] = $aftercolspan;
for($s=1;$s<=$afterspan;$s++){
$element->addCell(null,$cellRowContinue);
}
}
if (self::shouldAddTextRun($node)) {
return $cell->addTextRun(self::filterOutNonInheritedStyles(self::parseInlineStyle($node, $styles['paragraph'])));
}
return $cell;
}
Code html:
<table style="width: 100%; border: 1px #000000 solid;" cellspacing="0" collpadding="0">
<thead>
<tr style="background-color: #FF0000; text-align: center; color: #FFFFFF; font-weight: bold; ">
<th style="text-align:center;">A</th>
<th style="text-align:center;">B</th>
<th style="text-align:center;">C</th>
<th style="text-align:center;">D</th>
</tr>
</thead>
<tbody>
<tr><td > A1 </td><td colspan="2"> BC1 </td><td> D1 </td></tr>
<tr><td rowspan="2" colspan="2"> AB23 </td><td> C2 </td><td> D2 </td></tr>
<tr><td beforespan="1" beforecolspan="2" > C3 </td><td> D3 </td></tr>
<tr><td rowspan="3" > A456 </td><td> B4 </td><td rowspan="2" colspan="2"> CD45 </td></tr>
<tr><td rowspan="2" beforespan="1" afterspan="1" aftercolspan="2">B5</td></tr>
<tr><td beforespan="2">C6</td><td> D6 </td></tr>
<tr><td> A7 </td><td> B7 </td><td> C7 </td><td> D7 </td></tr>
<tr><td > A8 </td><td colspan="2"> BC8 </td><td > D8 </td></tr>
<tr><td colspan="3"> ABC9 </td><td rowspan="2"> D9 </td></tr>
<tr><td > A9 </td><td > B9 </td><td afterspan="1"> C9 </td></tr>
</tbody>
</table>
Output:

Thanks for sharing your piece of code but it does not fit my needs, because content is dynamic and only standards html tags and attributes will be available
update the code: phpoffice/phpword/src/PhpWord/Shared/Html.php
add this variable at the beginning of Html class:
protected static $rowIndex = 0;
protected static $columnIndex = 0;
protected static $rowSpanArray = [];
update parseTable method:
protected static function parseTable($node, $element, &$styles)
{
$elementStyles = self::parseInlineStyle($node, $styles['table']);
$newElement = $element->addTable($elementStyles);
// Add style name from CSS Class
if (isset($elementStyles['className'])) {
$newElement->getStyle()->setStyleName($elementStyles['className']);
}
self::$rowIndex = 0;
self::$rowSpanArray = [];
$attributes = $node->attributes;
if ($attributes->getNamedItem('border')) {
$border = (int) $attributes->getNamedItem('border')->nodeValue;
$newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border));
}
return $newElement;
}
update parseRow method:
protected static function parseRow($node, $element, &$styles)
{
$rowStyles = self::parseInlineStyle($node, $styles['row']);
if ($node->parentNode->nodeName == 'thead') {
$rowStyles['tblHeader'] = true;
}
// set cell height to control row heights
$height = $rowStyles['height'] ?? null;
unset($rowStyles['height']); // would not apply
self::$columnIndex = 0;
self::$rowIndex = self::$rowIndex + 1;
return $element->addRow($height, $rowStyles);
}
update parseCell method:
protected static function parseCell($node, $element, &$styles)
{
$cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
$vMergeStyle = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
self::$columnIndex = self::$columnIndex + 1;
$search_row_items = ["rowIndex" => self::$rowIndex];
$rowSpanCell = array_filter(self::$rowSpanArray, function ($item) use ($search_row_items) {
return count(array_intersect_assoc($search_row_items, $item)) == count($search_row_items);
});
$colspan = $node->getAttribute('colspan');
if (!empty($colspan)) {
$cellStyles['gridSpan'] = $colspan - 0;
self::$columnIndex = self::$columnIndex + $colspan - 1;
}
$rowspan = $node->getAttribute('rowspan');
if (!empty($rowspan)) {
$cellStyles['vMerge'] = 'restart';
$gridSpan = 0;
$colIndex = self::$columnIndex;
if (!empty($colspan)){
$gridSpan = $colspan;
$colIndex = self::$columnIndex - $colspan + 1;
}
for ($x = 1; $x < $rowspan; $x++) {
array_push(self::$rowSpanArray, ['columnIndex'=>$colIndex, 'rowIndex'=>self::$rowIndex + $x, 'colspan'=>$gridSpan]);
}
}
$search_column_item = ["columnIndex" => self::$columnIndex];
$currentColumnRowSpan = array_filter($rowSpanCell, function ($item) use ($search_column_item) {
return count(array_intersect_assoc($search_column_item, $item)) == count($search_column_item);
});
// set cell width to control column widths
$width = $cellStyles['width'] ?? null;
unset($cellStyles['width']); // would not apply
$loop_check = self::$columnIndex;
if (count($currentColumnRowSpan) == 0){
$cell = $element->addCell($width, $cellStyles);
$loop_check = self::$columnIndex + 1;
}
if (count($rowSpanCell) > 0) {
unset($vMergeStyle['width']);
foreach($rowSpanCell as $row) {
if($row['columnIndex'] == $loop_check){
$loop_check = $row['columnIndex'] + 1;
if ($row['colspan'] > 0) {
$vMergeStyle['gridSpan'] = $row['colspan'];
self::$columnIndex = self::$columnIndex + $row['colspan'] + 1;
} else {
unset($vMergeStyle['gridSpan']);
self::$columnIndex = self::$columnIndex + 1;
}
$vMergeStyle['vMerge'] = 'continue';
$element->addCell($width, $vMergeStyle);
}
}
}
if (count($currentColumnRowSpan) > 0){
$cell = $element->addCell($width, $cellStyles);
}
$search_item = ["columnIndex" => self::$columnIndex + 1];
$nextColumnRowSpan = array_filter($rowSpanCell, function ($item) use ($search_item) {
return count(array_intersect_assoc($search_item, $item)) == count($search_item);
});
if (count($nextColumnRowSpan) > 0) {
unset($vMergeStyle['width']);
$loop_check = self::$columnIndex + 1;
foreach($rowSpanCell as $row) {
if($row['columnIndex'] == $loop_check){
$loop_check = $row['columnIndex'] + 1;
if ($row['colspan'] > 0) {
$vMergeStyle['gridSpan'] = $row['colspan'];
self::$columnIndex = self::$columnIndex + $row['colspan'] + 1;
} else {
unset($vMergeStyle['gridSpan']);
self::$columnIndex = self::$columnIndex + 1;
}
$vMergeStyle['vMerge'] = 'continue';
$element->addCell($width, $vMergeStyle);
}
}
}
if (self::shouldAddTextRun($node)) {
return $cell->addTextRun(self::filterOutNonInheritedStyles(self::parseInlineStyle($node, $styles['paragraph'])));
}
return $cell;
}