PHPWord icon indicating copy to clipboard operation
PHPWord copied to clipboard

Is there any support for rowspan and colspan in addHTML ?

Open rameshsomepallidrg opened this issue 6 years ago • 8 comments

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. Screenshot 2019-06-13 at 2 35 31 PM

Please find the attached below screen shot for Original content. Screenshot 2019-06-19 at 4 15 43 PM

Any help on this!

Thanks, Ramesh S

rameshsomepallidrg avatar Jun 19 '19 11:06 rameshsomepallidrg

Any news? I'm having the same issue.

danilocarva9 avatar Aug 23 '19 15:08 danilocarva9

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.

0b10011 avatar Aug 30 '19 16:08 0b10011

Any news regarding rowspan support ? Would be worth value. Thanks

simogeo avatar Feb 10 '22 13:02 simogeo

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: image

yherus avatar Feb 17 '22 23:02 yherus

Hi @yherus : thanks for your proposal. I will try that soon.

And what about this ? https://github.com/PHPOffice/PHPWord/pull/2163

Thanks again

simogeo avatar Feb 18 '22 17:02 simogeo

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: image

yherus avatar Feb 19 '22 01:02 yherus

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

simogeo avatar Feb 19 '22 07:02 simogeo

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;
}

majeeed87 avatar Jun 04 '25 02:06 majeeed87