PHPWord icon indicating copy to clipboard operation
PHPWord copied to clipboard

feat: add support for rowspan in addHTML()

Open jnlin opened this issue 1 year ago • 2 comments

Description

This PR resolves #1643 . Thanks for @yherus for the initial code

Checklist:

  • [x] My CI is :green_circle:
  • [x] I have covered by unit tests my new code (check build/coverage for coverage report)
  • [x] I have updated the documentation to describe the changes
  • [x] I have updated the changelog

jnlin avatar Aug 07 '24 08:08 jnlin

Coverage Status

coverage: 97.181% (-0.03%) from 97.208% when pulling da62475f90767f63501d98b9f9aa9ef6b5c45f2a on WritePath:rowspan into f9ce80473fb6116e84fd8ae9b2460a0469b7e5f2 on PHPOffice:master.

coveralls avatar Aug 07 '24 08:08 coveralls

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