Sample Code:
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$section = $phpWord->addSection();
$html = '<table><tr><td>test</td></tr></table>';
\PhpOffice\PhpWord\Shared\Html::addHtml($section, $html);
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save('test.docx');
Expected Output:
Table with one cell containing the word "test".
Actual Output:
Blank
From stepping through the code quickly, the issue seems to be caused by the following if condition in parseChildNodes():
if ($element instanceof AbstractContainer) {
self::parseNode($cNode, $element, $styles, $data);
}
Commenting out the if condition then allows for the sample code above to produce the expected output.
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
Hello
me too, you have a solution? (develop branch)
Thank you
@hregis l do, in src/PhpWord/Shared/Html.php parseChildNodes():
private static function parseChildNodes($node, $element, $styles, $data)
{
if ($node->nodeName != 'li') {
$cNodes = $node->childNodes;
if (count($cNodes) > 0) {
foreach ($cNodes as $cNode) {
if ($element instanceof AbstractContainer) {
self::parseNode($cNode, $element, $styles, $data);
}
}
}
}
}
Change this to:
private static function parseChildNodes($node, $element, $styles, $data)
{
if ($node->nodeName != 'li') {
$cNodes = $node->childNodes;
if (count($cNodes) > 0) {
foreach ($cNodes as $cNode) {
// if ($element instanceof AbstractContainer) {
self::parseNode($cNode, $element, $styles, $data);
// }
}
}
}
}
Also could this issue please be labelled as a Bug, it's definitely not a Question.
@EK1771 thank you, but i have this error with develop branch:
Fatal error: Call to undefined method PhpOffice\PhpWord\Element\Table::addText() in /PhpWord/Shared/Html.php on line 239
I had the same issue where tables were not being parsed from HTML to DOM.
The problem is that HTML elements
<tbody> <tr> and <td> are not DOMElements as defined by the Abstract Container class. Because these HTML elements are not DOM Abstract Containers the parseChildNodes method doesnt check for any child elements.
@EK1771 solution removes the check against Abstract containers, but also causes every element to be checked for children, even when some are not containers.
There are a couple of steps to fix this.
1) Insert a new node into the HTML mapping table to catch <tbody> elements.
/PhpWord/Shared/Html.php:parseNode()
// Node mapping table
$nodes = array(
// $method $node $element $styles $data $argument1 $argument2
'p' => array('Paragraph', $node, $element, $styles, null, null, null),
'h1' => array('Heading', null, $element, $styles, null, 'Heading1', null),
'h2' => array('Heading', null, $element, $styles, null, 'Heading2', null),
'h3' => array('Heading', null, $element, $styles, null, 'Heading3', null),
'h4' => array('Heading', null, $element, $styles, null, 'Heading4', null),
'h5' => array('Heading', null, $element, $styles, null, 'Heading5', null),
'h6' => array('Heading', null, $element, $styles, null, 'Heading6', null),
'#text' => array('Text', $node, $element, $styles, null, null, null),
'span' => array('Span', $node, null, $styles, null, null, null), //to catch inline span style changes
'strong' => array('Property', null, null, $styles, null, 'bold', true),
'em' => array('Property', null, null, $styles, null, 'italic', true),
'sup' => array('Property', null, null, $styles, null, 'superScript', true),
'sub' => array('Property', null, null, $styles, null, 'subScript', true),
'table' => array('Table', $node, $element, $styles, null, 'addTable', true),
'tbody' => array('Table', $node, $element, $styles, null, 'skipTbody', true), //added to catch tbody in html.
'tr' => array('Table', $node, $element, $styles, null, 'addRow', true),
'td' => array('Table', $node, $element, $styles, null, 'addCell', true),
'ul' => array('List', null, null, $styles, $data, 3, null),
'ol' => array('List', null, null, $styles, $data, 7, null),
'li' => array('ListItem', $node, $element, $styles, $data, null, null),
);
2) Modify the parseChildNodes method. Define a list of table HTML elements which contain child elements. Write an IF check against the HTML nodeName. Let any other node types carry on to the original IF check for DOM Elements.
private static function parseChildNodes($node, $element, $styles, $data)
{
if ($node->nodeName != 'li') {
$cNodes = $node->childNodes;
if (count($cNodes) > 0) {
foreach ($cNodes as $cNode) {
// Added to get tables to work
$htmlContainers = array(
'tbody',
'tr',
'td',
);
if (in_array( $cNode->nodeName, $htmlContainers ) ) {
self::parseNode($cNode, $element, $styles, $data);
}
// All other containers as defined in AbstractContainer
if ($element instanceof AbstractContainer) {
self::parseNode($cNode, $element, $styles, $data);
}
}
}
}
}
3) Modify the parseTable method. The DOM writer adds columns and rows to the Table element directly, so you need to add a Switch or series of If checks against Argument1 of the Node Table.
private static function parseTable($node, $element, &$styles, $argument1)
{
switch ($argument1) {
case 'addTable':
$styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']);
$newElement = $element->addTable('table', array('width' => 90));
break;
case 'skipTbody':
$newElement = $element;
break;
case 'addRow':
$newElement = $element->addRow();
break;
case 'addCell':
$newElement = $element->addCell(1750);
break;
}
// $attributes = $node->attributes;
// if ($attributes->getNamedItem('width') !== null) {
// $newElement->setWidth($attributes->getNamedItem('width')->value);
// }
// if ($attributes->getNamedItem('height') !== null) {
// $newElement->setHeight($attributes->getNamedItem('height')->value);
// }
// if ($attributes->getNamedItem('width') !== null) {
// $newElement=$element->addCell($width=$attributes->getNamedItem('width')->value);
// }
return $newElement;
}
This works for me, hope it helps others. I'm sure there is a more elegent solution that could be incorporated in the Develop branch.
It needs to be exapanded to deal with <thead> and other HTML Table Elements.
Mark
Any news? The bug still occours
by using \PhpOffice\PhpWord\SharedHtml::addHtml($section, $html) we can interpret html to word. Can we set alignment options for this output (such as align right/left/both) ?
@mogilvie are you able to share your complete and working Html class with modifications? I'm trying it myself but when I try and apply it to an updated sample (as below), I get an error when it tries to write because of the objects is null.
Html class
//node mapping table
'table' => array('Table', $node, $element, $styles, null, 'addTable', true),
'thead' => array('Table', $node, $element, $styles, null, 'skipThead', true),
'tbody' => array('Table', $node, $element, $styles, null, 'skipTbody', true),
'tr' => array('Table', $node, $element, $styles, null, 'addRow', true),
'td' => array('Table', $node, $element, $styles, null, 'addCell', true),
'th' => array('Table', $node, $element, $styles, null, 'addCell', true),
/**
* Parse child nodes.
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array $styles
* @param array $data
* @return void
*/
private static function parseChildNodes($node, $element, $styles, $data)
{
if ('li' != $node->nodeName) {
$cNodes = $node->childNodes;
if (count($cNodes) > 0) {
foreach ($cNodes as $cNode) {
// Added to get tables to work
$htmlContainers = array(
'thead',
'tbody',
'tr',
'td',
'th',
);
if (in_array($cNode->nodeName, $htmlContainers)) {
self::parseNode($cNode, $element, $styles, $data);
}
if ($element instanceof AbstractContainer) {
self::parseNode($cNode, $element, $styles, $data);
}
}
}
}
}
private static function parseTable($node, $element, &$styles, $argument1)
{
switch ($argument1) {
case 'addTable':
$styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']);
$newElement = $element->addTable('table', array('width' => 90));
break;
case 'skipThead':
case 'skipTbody':
$newElement = $element;
break;
case 'addRow':
$newElement = $element->addRow();
break;
case 'addCell':
$newElement = $element->addCell(1750);
break;
}
Sample:
$html .= '<table><thead><tr><th>Header of column 1</th><th>Header of column 2</th></tr></thead>';
$html .= '<tbody><tr><td>Row 1 for column 1</td><td>Row 1 for column 2</td></tr></tbody></table>';
I'll post the full class tonight, but in the meantime, is the error caused by the switch statement not having any code to be executed for case: 'skipThead'?
@mogilvie I doubt that - it should fall through to the 'skipTbody' case because there's no break statement. I don't fully understand how this works but I _assumed_ that thead would need to be handled in the same way as tbody.
@garethellis36 Agree re skipThead. Is the parseTable function returning the $newElement? It's cut off from the sample html class.
Html.php uploaded as a text file. It was working as of Jan 2015.
Thank you, this is very very helpful. The embedded table is printing like charm now. I am still stuck with an embedded nested list, if you can help. There are two issues, 1. List item not printing if <strong> tag is used, 2. Nested list not printing with or without <strong> tags. Here's what I have: <p>The following list has all the information.</p><ol><li><strong>Item 1</strong><ol><li><strong>Nested Item 1</strong></li><li>Nested Item 2</li></ol></li></ol><p>List ends here.</p>
Thank you
@mogilvie Your Html.php works ok, thanks for your work. I had to comment line 61, $dom->save('/var/www/vhosts/specshaper.com/DOM.xml'); //@TODO Delete Debug
However, the table is generated without borders, even if a set a big "border" value in
| on Html table? It's not working :(
Was this page helpful?
0 / 5 - 0 ratings
Related issues |
Most helpful comment
I had the same issue where tables were not being parsed from HTML to DOM.
The problem is that HTML elements
<tbody> <tr> and <td>are not DOMElements as defined by the Abstract Container class. Because these HTML elements are not DOM Abstract Containers the parseChildNodes method doesnt check for any child elements.@EK1771 solution removes the check against Abstract containers, but also causes every element to be checked for children, even when some are not containers.
There are a couple of steps to fix this.
1) Insert a new node into the HTML mapping table to catch
<tbody>elements./PhpWord/Shared/Html.php:parseNode()
2) Modify the parseChildNodes method. Define a list of table HTML elements which contain child elements. Write an IF check against the HTML nodeName. Let any other node types carry on to the original IF check for DOM Elements.
3) Modify the parseTable method. The DOM writer adds columns and rows to the Table element directly, so you need to add a Switch or series of If checks against Argument1 of the Node Table.
This works for me, hope it helps others. I'm sure there is a more elegent solution that could be incorporated in the Develop branch.
It needs to be exapanded to deal with
<thead>and other HTML Table Elements.Mark