From aa151c40109bb7979655edf83dcb58b92177ae41 Mon Sep 17 00:00:00 2001 From: Bas-Jan 't Jong Date: Sat, 10 May 2014 14:03:16 +0200 Subject: [PATCH 1/3] Added function to add elements via HTML --- samples/Sample_26_Html.php | 19 +++ src/PhpWord/Shared/Html.php | 228 ++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 samples/Sample_26_Html.php create mode 100644 src/PhpWord/Shared/Html.php diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php new file mode 100644 index 00000000..9b46557d --- /dev/null +++ b/samples/Sample_26_Html.php @@ -0,0 +1,19 @@ +addSection(); +$html='

Adding element via HTML

'; +$html.='

Some well formed HTML snippet needs to be used

'; +$html.='

With for example some inline formatting'; + +\PhpOffice\PhpWord\Shared\Html::addHtml($section, $html); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} \ No newline at end of file diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php new file mode 100644 index 00000000..43a9b6cb --- /dev/null +++ b/src/PhpWord/Shared/Html.php @@ -0,0 +1,228 @@ +preserveWhiteSpace = true; + $dom->loadXML('' . html_entity_decode($html) . ''); + + $node = $dom->getElementsByTagName('body'); + + self::parseNode($node->item(0), $object); + } + + /** + * parse Inline style of a node + * + * @param $node node to check on attributes and to compile a style array + * @param $style is supplied, the inline style attributes are added to the already existing style + * + */ + protected static function parseInlineStyle($node, $style = array()) + { + if ($node->nodeType == XML_ELEMENT_NODE) { + $attributes = $node->attributes; // get all the attributes(eg: id, class) + + 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; + } + } + break; + } + } + } + return $style; + } + + /** + * parse a node and add a corresponding element to the object + * + * @param $node node to parse + * @param $object object to add an element corresponding with the node + * @param $styles array with all styles + * @param $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']); + $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': + $styles['paragraphStyle'] = self::parseInlineStyle($node, $styles['paragraphStyle']); + $newobject = $object->addRow(); + // if ($attributes->getNamedItem('height') !== null)$newobject->setHeight($attributes->getNamedItem('height')->value); + break; + case 'td': + $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) { + foreach ($cNodes as $cNode) { + if ($cNode->nodeName == '#text') { + $text = $cNode->nodeValue; + } + } + $object->addListItem($text, $data['listdepth'], $styles['fontStyle'], $styles['listStyle'], $styles['paragraphStyle']); + } + } + + if ($newobject === null) { + $newobject = $object; + } + + /* + * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete condition + */ + if ($node->nodeName != 'li') { + $cNodes = $node->childNodes; + if (count($cNodes) > 0) { + foreach ($cNodes as $cNode) { + self::parseNode($cNode, $newobject, $styles, $data); + } + } + } + } +} From 07b4ae2c0fcf91f7ca861a683ef25f4337789eba Mon Sep 17 00:00:00 2001 From: Bas-Jan 't Jong Date: Sat, 10 May 2014 14:04:15 +0200 Subject: [PATCH 2/3] Added possiblity to add tables inside textbox --- src/PhpWord/Element/TextBox.php | 15 +++++++++++++++ src/PhpWord/Writer/Word2007/Element/TextBox.php | 2 -- src/PhpWord/Writer/Word2007/Part/AbstractPart.php | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Element/TextBox.php b/src/PhpWord/Element/TextBox.php index 1de74649..979b7c1f 100644 --- a/src/PhpWord/Element/TextBox.php +++ b/src/PhpWord/Element/TextBox.php @@ -53,4 +53,19 @@ class TextBox extends AbstractContainer { return $this->style; } + + /** + * Add table element + * + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\Table + * @todo Merge with the same function on Footer + */ + public function addTable($style = null) + { + $table = new Table($this->getDocPart(), $this->getDocPartId(), $style); + $this->addElement($table); + + return $table; + } } diff --git a/src/PhpWord/Writer/Word2007/Element/TextBox.php b/src/PhpWord/Writer/Word2007/Element/TextBox.php index fd1683ae..b732ffab 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBox.php @@ -57,9 +57,7 @@ class TextBox extends Element $margins = implode(', ', $tbxStyle->getInnerMargin()); $this->xmlWriter->writeAttribute('inset', $margins); $this->xmlWriter->startElement('w:txbxContent'); - $this->xmlWriter->startElement('w:p'); $this->parentWriter->writeContainerElements($this->xmlWriter, $this->element); - $this->xmlWriter->endElement(); // w:p $this->xmlWriter->endElement(); // w:txbxContent $this->xmlWriter->endElement(); // v: textbox $styleWriter->writeW10Wrap(); diff --git a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php index d1717e9b..b09613a6 100644 --- a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php +++ b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php @@ -110,7 +110,7 @@ abstract class AbstractPart // Loop through elements $elements = $container->getElements(); - $withoutP = in_array($containerName, array('TextRun', 'Footnote', 'Endnote', 'TextBox')) ? true : false; + $withoutP = in_array($containerName, array('TextRun', 'Footnote', 'Endnote')) ? true : false; if (count($elements) > 0) { foreach ($elements as $element) { if ($element instanceof AbstractElement) { From 62ed72503228bc2fe785f49a1eed80cc19dbd320 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Sat, 10 May 2014 21:38:44 +0700 Subject: [PATCH 3/3] Some modifications for the new `Html::addHtml` feature --- .scrutinizer.yml | 9 ++++ .travis.yml | 2 +- CHANGELOG.md | 2 + phpmd.xml.dist | 20 +++++++++ samples/Sample_26_Html.php | 6 +-- src/PhpWord/Element/AbstractContainer.php | 54 +++++++++++++++++------ src/PhpWord/Element/Footer.php | 15 ------- src/PhpWord/Element/Section.php | 15 ------- src/PhpWord/Element/Table.php | 3 +- src/PhpWord/Element/TextBox.php | 15 ------- src/PhpWord/Shared/Html.php | 43 +++++++++--------- 11 files changed, 98 insertions(+), 86 deletions(-) create mode 100644 phpmd.xml.dist diff --git a/.scrutinizer.yml b/.scrutinizer.yml index ab5f8620..d253de30 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,6 +5,15 @@ before_commands: - "composer install --prefer-source --dev" tools: + php_code_sniffer: + enabled: true + config: + standard: PSR2 + php_cpd: true + php_mess_detector: + enabled: true + config: + ruleset: phpmd.xml.dist external_code_coverage: enabled: true timeout: 900 diff --git a/.travis.yml b/.travis.yml index 7593379c..190c0a3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,7 @@ script: ## PHP Copy/Paste Detector - php phpcpd.phar src/ tests/ --verbose ## PHP Mess Detector - - phpmd src/,tests/ text unusedcode,naming,design,controversial --exclude pclzip.lib.php + - phpmd src/,tests/ text ./phpmd.xml.dist --exclude pclzip.lib.php ## PHPLOC #- php phploc.phar src/ ## PHPUnit diff --git a/CHANGELOG.md b/CHANGELOG.md index f23ca147..403a32bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This release changed PHPWord license from LGPL 2.1 to LGPL 3. - Image: Ability to define relative and absolute positioning - @basjan GH-217 - Footer: Conform footer with header by adding firstPage, evenPage and by inheritance - @basjan @ivanlanin GH-219 - TextBox: Ability to add textbox in section, header, and footer - @basjan @ivanlanin GH-228 +- TextBox: Ability to add textbox in table - @basjan GH-231 +- HTML: Ability to add elements to PHPWord object via html - @basjan GH-231 ### Bugfixes diff --git a/phpmd.xml.dist b/phpmd.xml.dist new file mode 100644 index 00000000..18d5b2a9 --- /dev/null +++ b/phpmd.xml.dist @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index 9b46557d..58436915 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -6,9 +6,9 @@ echo date('H:i:s') , ' Create new PhpWord object' , EOL; $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 = '

Adding element via HTML

'; +$html .= '

Some well formed HTML snippet needs to be used

'; +$html .= '

With for example some inline formatting

'; \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html); diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 8b72f660..431a3066 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -66,6 +66,31 @@ abstract class AbstractContainer extends AbstractElement return count($this->elements); } + /** + * Add generic element with style + * + * This is how all elements should be added with dependency injection: with + * just one simple $style. Currently this function supports TextRun, Table, + * and TextBox since all other elements have different arguments + * + * @todo Change the function name into something better? + * + * @param string $elementName + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\AbstractElement + */ + private function addGenericElement($elementName, $style) + { + $elementClass = __NAMESPACE__ . '\\' . $elementName; + + $this->checkValidity($elementName); + $element = new $elementClass($style); + $element->setDocPart($this->getDocPart(), $this->getDocPartId()); + $this->addElement($element); + + return $element; + } + /** * Add text/preservetext element * @@ -100,13 +125,7 @@ abstract class AbstractContainer extends AbstractElement */ public function addTextRun($paragraphStyle = null) { - $this->checkValidity('TextRun'); - - $element = new TextRun($paragraphStyle); - $element->setDocPart($this->getDocPart(), $this->getDocPartId()); - $this->addElement($element); - - return $element; + return $this->addGenericElement('TextRun', $paragraphStyle); } /** @@ -186,6 +205,18 @@ abstract class AbstractContainer extends AbstractElement return $element; } + /** + * Add table element + * + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\Table + * @todo Merge with the same function on Footer + */ + public function addTable($style = null) + { + return $this->addGenericElement('Table', $style); + } + /** * Add image element * @@ -302,13 +333,7 @@ abstract class AbstractContainer extends AbstractElement */ public function addTextBox($style = null) { - $this->checkValidity('TextBox'); - - $textbox = new TextBox($style); - $textbox->setDocPart($this->getDocPart(), $this->getDocPartId()); - $this->addElement($textbox); - - return $textbox; + return $this->addGenericElement('TextBox', $style); } /** @@ -329,6 +354,7 @@ abstract class AbstractContainer extends AbstractElement 'Object' => $allContainers, 'TextRun' => array('section', 'header', 'footer', 'cell', 'textbox'), 'ListItem' => array('section', 'header', 'footer', 'cell', 'textbox'), + 'Table' => array('section', 'header', 'footer', 'textbox'), 'CheckBox' => array('section', 'header', 'footer', 'cell'), 'TextBox' => array('section', 'header', 'footer', 'cell'), 'Footnote' => array('section', 'textrun', 'cell'), diff --git a/src/PhpWord/Element/Footer.php b/src/PhpWord/Element/Footer.php index 2f23a3d3..265c2c4c 100644 --- a/src/PhpWord/Element/Footer.php +++ b/src/PhpWord/Element/Footer.php @@ -114,19 +114,4 @@ class Footer extends AbstractContainer { return $this->type = self::EVEN; } - - /** - * Add table element - * - * @param mixed $style - * @return \PhpOffice\PhpWord\Element\Table - * @todo Merge with the same function on Section - */ - public function addTable($style = null) - { - $table = new Table($this->getDocPart(), $this->getDocPartId(), $style); - $this->addElement($table); - - return $table; - } } diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index 9067c73c..04ff5aa2 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -117,21 +117,6 @@ class Section extends AbstractContainer $this->addElement(new PageBreak()); } - /** - * Add table element - * - * @param mixed $style - * @return \PhpOffice\PhpWord\Element\Table - * @todo Merge with the same function on Footer - */ - public function addTable($style = null) - { - $table = new Table($this->getDocPart(), $this->getDocPartId(), $style); - $this->addElement($table); - - return $table; - } - /** * Add a Table-of-Contents Element * diff --git a/src/PhpWord/Element/Table.php b/src/PhpWord/Element/Table.php index af802d12..87d44e41 100644 --- a/src/PhpWord/Element/Table.php +++ b/src/PhpWord/Element/Table.php @@ -53,9 +53,8 @@ class Table extends AbstractElement * @param integer $docPartId * @param mixed $style */ - public function __construct($docPart, $docPartId, $style = null) + public function __construct($style = null) { - $this->setDocPart($docPart, $docPartId); $this->style = $this->setStyle(new TableStyle(), $style); } diff --git a/src/PhpWord/Element/TextBox.php b/src/PhpWord/Element/TextBox.php index 56645321..c3c83d81 100644 --- a/src/PhpWord/Element/TextBox.php +++ b/src/PhpWord/Element/TextBox.php @@ -53,19 +53,4 @@ class TextBox extends AbstractContainer { return $this->style; } - - /** - * Add table element - * - * @param mixed $style - * @return \PhpOffice\PhpWord\Element\Table - * @todo Merge with the same function on Footer - */ - public function addTable($style = null) - { - $table = new Table($this->getDocPart(), $this->getDocPartId(), $style); - $this->addElement($table); - - return $table; - } } diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 43a9b6cb..bcefb985 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -22,28 +22,29 @@ namespace PhpOffice\PhpWord\Shared; */ class Html { - /** - * add HTML parts + * Add HTML parts + * + * Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $object Where the parts need to be added + * @param string $html the code to parse * - * @param $object where the parts need to be added - * @param $html the code to parse - * */ - public static function addHtml($object, $html, $stylesheet = '') + public static function addHtml($object, $html) { /* - * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, + * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, * which could be applied when such an element occurs in the parseNode function. */ $html = str_replace(array("\n","\r"), '', $html); - + $dom = new \DOMDocument(); $dom->preserveWhiteSpace = true; $dom->loadXML('' . html_entity_decode($html) . ''); - + $node = $dom->getElementsByTagName('body'); - + self::parseNode($node->item(0), $object); } @@ -58,7 +59,7 @@ class Html { if ($node->nodeType == XML_ELEMENT_NODE) { $attributes = $node->attributes; // get all the attributes(eg: id, class) - + foreach ($attributes as $attribute) { switch ($attribute->name) { case 'style': @@ -94,7 +95,7 @@ class Html } return $style; } - + /** * parse a node and add a corresponding element to the object * @@ -112,8 +113,8 @@ class Html $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 */ @@ -157,8 +158,8 @@ class Html case 'sub': $styles['fontStyle']['subScript'] = true; break; - - /* + + /** * @todo As soon as TableItem, RowItem and CellItem support relative width and height */ case 'table': @@ -193,8 +194,8 @@ class Html } $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': @@ -208,12 +209,12 @@ class Html $object->addListItem($text, $data['listdepth'], $styles['fontStyle'], $styles['listStyle'], $styles['paragraphStyle']); } } - + if ($newobject === null) { $newobject = $object; } - - /* + + /** * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete condition */ if ($node->nodeName != 'li') {