From 0164e37873bf45f1cac86b6e995c32bf9e9b9b6e Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 31 May 2014 01:30:59 +0700 Subject: [PATCH] Decompose Shared\Html --- samples/Sample_26_Html.php | 6 +- src/PhpWord/Shared/Html.php | 440 ++++++++++++++++++++------------ src/PhpWord/Style/Paragraph.php | 1 + 3 files changed, 280 insertions(+), 167 deletions(-) diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index 58436915..92b3aa49 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -8,7 +8,11 @@ $phpWord = new \PhpOffice\PhpWord\PhpWord(); $section = $phpWord->addSection(); $html = '

Adding element via HTML

'; $html .= '

Some well formed HTML snippet needs to be used

'; -$html .= '

With for example some inline formatting

'; +$html .= '

With for example some1 inline formatting1

'; +$html .= '

Unordered (bulleted) list:

'; +$html .= ''; +$html .= '

Ordered (numbered) list:

'; +$html .= '
  1. Item 1
  2. Item 2
'; \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html); diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 171cfa21..bfe64a25 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -17,8 +17,12 @@ namespace PhpOffice\PhpWord\Shared; +use PhpOffice\PhpWord\Element\AbstractContainer; + /** * Common Html functions + * + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode */ class Html { @@ -27,10 +31,10 @@ class Html * * Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $object Where the parts need to be added + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added * @param string $html the code to parse */ - public static function addHtml($object, $html) + public static function addHtml($element, $html) { /* * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, @@ -44,17 +48,17 @@ class Html $node = $dom->getElementsByTagName('body'); - self::parseNode($node->item(0), $object); + self::parseNode($node->item(0), $element); } /** * parse Inline style of a node * * @param \DOMNode $node Node to check on attributes and to compile a style array - * @param array $style is supplied, the inline style attributes are added to the already existing style + * @param array $styles is supplied, the inline style attributes are added to the already existing style * @return array */ - protected static function parseInlineStyle($node, $style = array()) + protected static function parseInlineStyle($node, $styles = array()) { if ($node->nodeType == XML_ELEMENT_NODE) { $attributes = $node->attributes; // get all the attributes(eg: id, class) @@ -62,191 +66,295 @@ class Html foreach ($attributes as $attribute) { switch ($attribute->name) { case 'style': - $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); - foreach ($properties as $property) { - list ($cKey, $cValue) = explode(':', $property, 2); - $cValue = trim($cValue); - switch (trim($cKey)) { - case 'text-decoration': - switch ($cValue) { - case 'underline': - $style['underline'] = 'single'; - break; - case 'line-through': - $style['strikethrough'] = true; - break; - } - break; - case 'text-align': - $style['align'] = $cValue; - break; - case 'color': - $style['color'] = trim($cValue, "#"); - break; - case 'background-color': - $style['bgColor'] = trim($cValue, "#"); - break; - } - } + $styles = self::parseStyle($attribute, $styles); break; } } } - return $style; + return $styles; } /** - * parse a node and add a corresponding element to the object + * Parse a node and add a corresponding element to the parent element * * @param \DOMNode $node node to parse - * @param \PhpOffice\PhpWord\Element\AbstractContainer $object object to add an element corresponding with the node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node * @param array $styles Array with all styles * @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems */ - protected static function parseNode( - $node, - $object, - $styles = array('fontStyle' => array(), 'paragraphStyle' => array(), 'listStyle' => array()), - $data = array() - ) { - $newobject = null; - switch ($node->nodeName) { - case 'p': - $styles['paragraphStyle'] = self::parseInlineStyle($node, $styles['paragraphStyle']); - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - - /** - * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that - * Heading1 - Heading6 are already defined somewhere - */ - case 'h1': - $styles['paragraphStyle'] = 'Heading1'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case 'h2': - $styles['paragraphStyle'] = 'Heading2'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case 'h3': - $styles['paragraphStyle'] = 'Heading3'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case 'h4': - $styles['paragraphStyle'] = 'Heading4'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case 'h5': - $styles['paragraphStyle'] = 'Heading5'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case 'h6': - $styles['paragraphStyle'] = 'Heading6'; - $newobject = $object->addTextRun($styles['paragraphStyle']); - break; - case '#text': - $styles['fontStyle'] = self::parseInlineStyle($node, $styles['fontStyle']); - if (method_exists($object, 'addText')) { - $object->addText($node->nodeValue, $styles['fontStyle'], $styles['paragraphStyle']); - } - break; - case 'strong': - $styles['fontStyle']['bold'] = true; - break; - case 'em': - $styles['fontStyle']['italic'] = true; - break; - case 'sup': - $styles['fontStyle']['superScript'] = true; - break; - case 'sub': - $styles['fontStyle']['subScript'] = true; - break; - - /** - * @todo As soon as TableItem, RowItem and CellItem support relative width and height - */ - case 'table': - $styles['paragraphStyle'] = self::parseInlineStyle($node, $styles['paragraphStyle']); - $newobject = $object->addTable(); - // if ($attributes->getNamedItem('width') !== null) { - // $newobject->setWidth($attributes->getNamedItem('width')->value); - // } - break; - case 'tr': - /** @var \PhpOffice\PhpWord\Element\Table $object Type hint */ - $styles['paragraphStyle'] = self::parseInlineStyle($node, $styles['paragraphStyle']); - $newobject = $object->addRow(); - // if ($attributes->getNamedItem('height') !== null) { - // $newobject->setHeight($attributes->getNamedItem('height')->value); - // } - break; - case 'td': - /** @var \PhpOffice\PhpWord\Element\Row $object Type hint */ - $styles['paragraphStyle'] = self::parseInlineStyle($node, $styles['paragraphStyle']); - // if ($attributes->getNamedItem('width') !== null) { - // $newobject=$object->addCell($width=$attributes->getNamedItem('width')->value); - // } else { - // $newobject=$object->addCell(); - // } - $newobject = $object->addCell(); - break; - case 'ul': - if (isset($data['listdepth'])) { - $data['listdepth'] ++; - } else { - $data['listdepth'] = 0; - } - $styles['listStyle']['listType'] = 3; // TYPE_BULLET_FILLED = 3; - break; - case 'ol': - if (isset($data['listdepth'])) { - $data['listdepth'] ++; - } else { - $data['listdepth'] = 0; - } - $styles['listStyle']['listType'] = 7; // TYPE_NUMBER = 7; - break; - - /** - * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes - */ - case 'li': - $cNodes = $node->childNodes; - if (count($cNodes) > 0) { - $text = ''; - foreach ($cNodes as $cNode) { - if ($cNode->nodeName == '#text') { - $text = $cNode->nodeValue; - } - } - $object->addListItem( - $text, - $data['listdepth'], - $styles['fontStyle'], - $styles['listStyle'], - $styles['paragraphStyle'] - ); - } + protected static function parseNode($node, $element, $styles = array(), $data = array()) + { + // Populate styles array + $styleTypes = array('font', 'paragraph', 'list'); + foreach ($styleTypes as $styleType) { + if (!isset($styles[$styleType])) { + $styles[$styleType] = array(); + } } - if ($newobject === null) { - $newobject = $object; + // 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), + '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), + '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), + ); + + $newElement = null; + $keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2'); + + if (array_key_exists($node->nodeName, $nodes)) { + + // Execute method based on node mapping table and return $newElement or null + // Arguments are passed by reference + $arguments = array(); + $args = array(); + list($method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) = $nodes[$node->nodeName]; + for ($i = 0; $i <= 5; $i++) { + if ($args[$i] !== null) { + $arguments[$keys[$i]] = &$args[$i]; + } + } + $method = "parse{$method}"; + $newElement = call_user_func_array(array('PhpOffice\PhpWord\Shared\Html', $method), $arguments); + + // Retrieve back variables from arguments + foreach ($keys as $key) { + if (array_key_exists($key, $arguments)) { + $$key = $arguments[$key]; + } + } } - /** - * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete condition - */ + if ($newElement === null) { + $newElement = $element; + } + + self::parseChildNodes($node, $newElement, $styles, $data); + } + + /** + * Parse child nodes + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @param array $data + */ + private static function parseChildNodes($node, $element, $styles, $data) + { if ($node->nodeName != 'li') { $cNodes = $node->childNodes; if (count($cNodes) > 0) { foreach ($cNodes as $cNode) { - if ($newobject instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { - self::parseNode($cNode, $newobject, $styles, $data); + if ($element instanceof AbstractContainer) { + self::parseNode($cNode, $element, $styles, $data); } } } } } + + /** + * Parse paragraph node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @return \PhpOffice\PhpWord\Element\TextRun + */ + private static function parseParagraph($node, $element, &$styles) + { + $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']); + $newElement = $element->addTextRun($styles['paragraph']); + + return $newElement; + } + + /** + * Parse heading node + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @param string $argument1 Name of heading style + * @return \PhpOffice\PhpWord\Element\TextRun + * + * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that + * Heading1 - Heading6 are already defined somewhere + */ + private static function parseHeading($element, &$styles, $argument1) + { + $styles['paragraph'] = $argument1; + $newElement = $element->addTextRun($styles['paragraph']); + + return $newElement; + } + + /** + * Parse text node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @return null + */ + private static function parseText($node, $element, &$styles) + { + $styles['font'] = self::parseInlineStyle($node, $styles['font']); + if (method_exists($element, 'addText')) { + $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + } + + return null; + } + + /** + * Parse property node + * + * @param array $styles + * @param string $argument1 Style name + * @param string $argument2 Style value + * @return null + */ + private static function parseProperty(&$styles, $argument1, $argument2) + { + $styles['font'][$argument1] = $argument2; + + return null; + } + + /** + * Parse table node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @param string $argument1 Method name + * @return \PhpOffice\PhpWord\Element\AbstractContainer $element + * + * @todo As soon as TableItem, RowItem and CellItem support relative width and height + */ + private static function parseTable($node, $element, &$styles, $argument1) + { + $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']); + + $newElement = $element->$argument1(); + + // $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; + } + + /** + * Parse list node + * + * @param array $styles + * @param array $data + * @param string $argument1 List type + * @return null + */ + private static function parseList(&$styles, &$data, $argument1) + { + if (isset($data['listdepth'])) { + $data['listdepth']++; + } else { + $data['listdepth'] = 0; + } + $styles['list']['listType'] = $argument1; + + return null; + } + + /** + * Parse list item node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @param array $data + * @return null + * + * @todo This function is almost the same like `parseChildNodes`. Merged? + * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes + */ + private static function parseListItem($node, $element, &$styles, $data) + { + $cNodes = $node->childNodes; + if (count($cNodes) > 0) { + $text = ''; + foreach ($cNodes as $cNode) { + if ($cNode->nodeName == '#text') { + $text = $cNode->nodeValue; + } + } + $element->addListItem($text, $data['listdepth'], $styles['font'], $styles['list'], $styles['paragraph']); + } + + return null; + } + + /** + * Parse style + * + * @param \DOMAttr $attribute + * @param array $styles + * @return array + */ + private static function parseStyle($attribute, $styles) + { + $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); + foreach ($properties as $property) { + list($cKey, $cValue) = explode(':', $property, 2); + $cValue = trim($cValue); + switch (trim($cKey)) { + case 'text-decoration': + switch ($cValue) { + case 'underline': + $styles['underline'] = 'single'; + break; + case 'line-through': + $styles['strikethrough'] = true; + break; + } + break; + case 'text-align': + $styles['align'] = $cValue; + break; + case 'color': + $styles['color'] = trim($cValue, "#"); + break; + case 'background-color': + $styles['bgColor'] = trim($cValue, "#"); + break; + } + } + + return $styles; + } } diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 32c7f0f8..81673586 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -186,6 +186,7 @@ class Paragraph extends AbstractStyle * reduce function call and increase cohesion between functions. Should be * implemented in all styles. * + * @ignoreScrutinizerPatch * @return array */ public function getStyleValues()