$listIndex
$listIndex :
Common Html functions
addHtml(\PhpOffice\PhpWord\Element\AbstractContainer $element, string $html, boolean $fullHTML = false, boolean $preserveWhiteSpace = true, $options = null)
Add HTML parts.
Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter
Warning: Do not pass user-generated HTML here, as that would allow an attacker to read arbitrary
files or perform server-side request forgery by passing local file paths or URLs in .
\PhpOffice\PhpWord\Element\AbstractContainer | $element | Where the parts need to be added |
string | $html | The code to parse |
boolean | $fullHTML | If it's a full HTML, no need to add 'body' tag |
boolean | $preserveWhiteSpace | If false, the whitespaces between nodes will be removed |
$options |
parseInlineStyle(\DOMNode $node, array $styles = array()) : array
parse Inline style of a node
\DOMNode | $node | Node to check on attributes and to compile a style array |
array | $styles | is supplied, the inline style attributes are added to the already existing style |
parseNode(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, array $styles = array(), array $data = array())
Parse a node and add a corresponding element to the parent element.
\DOMNode | $node | node to parse |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | object to add an element corresponding with the node |
array | $styles | Array with all styles |
array | $data | Array to transport data to a next level in the DOM tree, for example level of listitems |
parseChildNodes(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, array $styles, array $data)
Parse child nodes.
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
array | $styles | |
array | $data |
parseParagraph(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, $styles) : \PhpOffice\PhpWord\Element\TextRun
Parse paragraph node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles |
parseHeading(\PhpOffice\PhpWord\Element\AbstractContainer $element, $styles, string $argument1) : \PhpOffice\PhpWord\Element\TextRun
Parse heading node
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles | ||
string | $argument1 | Name of heading style |
parseText(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, $styles)
Parse text node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles |
parseTable(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, $styles) : \PhpOffice\PhpWord\Element\Table
Parse table node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles |
$element
parseRow(\DOMNode $node, \PhpOffice\PhpWord\Element\Table $element, $styles) : \PhpOffice\PhpWord\Element\Row
Parse a table row
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\Table | $element | |
$styles |
$element
parseCell(\DOMNode $node, \PhpOffice\PhpWord\Element\Table $element, $styles) : \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun
Parse table cell
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\Table | $element | |
$styles |
$element
parseList(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, $styles, $data)
Parse list node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles | ||
$data |
parseListItem(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, $styles, array $data)
Parse list item node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
$styles | ||
array | $data |
parseImage(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element) : \PhpOffice\PhpWord\Element\Image
Parse image node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element |
parseLineBreak(\PhpOffice\PhpWord\Element\AbstractContainer $element)
Parse line break
\PhpOffice\PhpWord\Element\AbstractContainer | $element |
parseLink(\DOMNode $node, \PhpOffice\PhpWord\Element\AbstractContainer $element, array $styles)
Parse link node
\DOMNode | $node | |
\PhpOffice\PhpWord\Element\AbstractContainer | $element | |
array | $styles |
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\Row;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\NumberFormat;
/**
* Common Html functions
*
* @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode
*/
class Html
{
private static $listIndex = 0;
private static $xpath;
private static $options;
/**
* Add HTML parts.
*
* Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter
* Warning: Do not pass user-generated HTML here, as that would allow an attacker to read arbitrary
* files or perform server-side request forgery by passing local file paths or URLs in <img>.
*
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added
* @param string $html The code to parse
* @param bool $fullHTML If it's a full HTML, no need to add 'body' tag
* @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed
* @param array $options:
* + IMG_SRC_SEARCH: optional to speed up images loading from remote url when files can be found locally
* + IMG_SRC_REPLACE: optional to speed up images loading from remote url when files can be found locally
*/
public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true, $options = null)
{
/*
* @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.
*/
self::$options = $options;
// Preprocess: remove all line ends, decode HTML entity,
// fix ampersand and angle brackets and add body tag for HTML fragments
$html = str_replace(array("\n", "\r"), '', $html);
$html = str_replace(array('<', '>', '&'), array('_lt_', '_gt_', '_amp_'), $html);
$html = html_entity_decode($html, ENT_QUOTES, 'UTF-8');
$html = str_replace('&', '&', $html);
$html = str_replace(array('_lt_', '_gt_', '_amp_'), array('<', '>', '&'), $html);
if (false === $fullHTML) {
$html = '<body>' . $html . '</body>';
}
// Load DOM
libxml_disable_entity_loader(true);
$dom = new \DOMDocument();
$dom->preserveWhiteSpace = $preserveWhiteSpace;
$dom->loadXML($html);
self::$xpath = new \DOMXPath($dom);
$node = $dom->getElementsByTagName('body');
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 $styles is supplied, the inline style attributes are added to the already existing style
* @return array
*/
protected static function parseInlineStyle($node, $styles = array())
{
if (XML_ELEMENT_NODE == $node->nodeType) {
$attributes = $node->attributes; // get all the attributes(eg: id, class)
foreach ($attributes as $attribute) {
switch ($attribute->name) {
case 'style':
$styles = self::parseStyle($attribute, $styles);
break;
case 'align':
$styles['alignment'] = self::mapAlign($attribute->value);
break;
case 'lang':
$styles['lang'] = $attribute->value;
break;
}
}
}
return $styles;
}
/**
* Parse a node and add a corresponding element to the parent element.
*
* @param \DOMNode $node node to parse
* @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, $element, $styles = array(), $data = array())
{
// Populate styles array
$styleTypes = array('font', 'paragraph', 'list', 'table', 'row', 'cell');
foreach ($styleTypes as $styleType) {
if (!isset($styles[$styleType])) {
$styles[$styleType] = array();
}
}
// 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),
'b' => array('Property', null, null, $styles, null, 'bold', true),
'em' => array('Property', null, null, $styles, null, 'italic', true),
'i' => array('Property', null, null, $styles, null, 'italic', true),
'u' => array('Property', null, null, $styles, null, 'underline', 'single'),
'sup' => array('Property', null, null, $styles, null, 'superScript', true),
'sub' => array('Property', null, null, $styles, null, 'subScript', true),
'span' => array('Span', $node, null, $styles, null, null, null),
'font' => array('Span', $node, null, $styles, null, null, null),
'table' => array('Table', $node, $element, $styles, null, null, null),
'tr' => array('Row', $node, $element, $styles, null, null, null),
'td' => array('Cell', $node, $element, $styles, null, null, null),
'th' => array('Cell', $node, $element, $styles, null, null, null),
'ul' => array('List', $node, $element, $styles, $data, null, null),
'ol' => array('List', $node, $element, $styles, $data, null, null),
'li' => array('ListItem', $node, $element, $styles, $data, null, null),
'img' => array('Image', $node, $element, $styles, null, null, null),
'br' => array('LineBreak', null, $element, $styles, null, null, null),
'a' => array('Link', $node, $element, $styles, null, null, null),
);
$newElement = null;
$keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2');
if (isset($nodes[$node->nodeName])) {
// 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];
}
}
}
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 ('li' != $node->nodeName) {
$cNodes = $node->childNodes;
if (!empty($cNodes)) {
foreach ($cNodes as $cNode) {
if ($element instanceof AbstractContainer || $element instanceof Table || $element instanceof Row) {
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::recursiveParseStylesInHierarchy($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
*/
private static function parseText($node, $element, &$styles)
{
$styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']);
//alignment applies on paragraph, not on font. Let's copy it there
if (isset($styles['font']['alignment']) && is_array($styles['paragraph'])) {
$styles['paragraph']['alignment'] = $styles['font']['alignment'];
}
if (is_callable(array($element, 'addText'))) {
$element->addText($node->nodeValue, $styles['font'], $styles['paragraph']);
}
}
/**
* Parse property node
*
* @param array &$styles
* @param string $argument1 Style name
* @param string $argument2 Style value
*/
private static function parseProperty(&$styles, $argument1, $argument2)
{
$styles['font'][$argument1] = $argument2;
}
/**
* Parse span node
*
* @param \DOMNode $node
* @param array &$styles
*/
private static function parseSpan($node, &$styles)
{
self::parseInlineStyle($node, $styles['font']);
}
/**
* Parse table node
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array &$styles
* @return Table $element
*
* @todo As soon as TableItem, RowItem and CellItem support relative width and height
*/
private static function parseTable($node, $element, &$styles)
{
$elementStyles = self::parseInlineStyle($node, $styles['table']);
$newElement = $element->addTable($elementStyles);
// $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 a table row
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\Table $element
* @param array &$styles
* @return Row $element
*/
private static function parseRow($node, $element, &$styles)
{
$rowStyles = self::parseInlineStyle($node, $styles['row']);
if ($node->parentNode->nodeName == 'thead') {
$rowStyles['tblHeader'] = true;
}
return $element->addRow(null, $rowStyles);
}
/**
* Parse table cell
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\Table $element
* @param array &$styles
* @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element
*/
private static function parseCell($node, $element, &$styles)
{
$cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']);
$colspan = $node->getAttribute('colspan');
if (!empty($colspan)) {
$cellStyles['gridSpan'] = $colspan - 0;
}
$cell = $element->addCell(null, $cellStyles);
if (self::shouldAddTextRun($node)) {
return $cell->addTextRun(self::parseInlineStyle($node, $styles['paragraph']));
}
return $cell;
}
/**
* Checks if $node contains an HTML element that cannot be added to TextRun
*
* @param \DOMNode $node
* @return bool Returns true if the node contains an HTML element that cannot be added to TextRun
*/
private static function shouldAddTextRun(\DOMNode $node)
{
$containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0;
if ($containsBlockElement) {
return false;
}
return true;
}
/**
* Recursively parses styles on parent nodes
* TODO if too slow, add caching of parent nodes, !! everything is static here so watch out for concurrency !!
*
* @param \DOMNode $node
* @param array &$styles
*/
private static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style)
{
$parentStyle = self::parseInlineStyle($node, array());
$style = array_merge($parentStyle, $style);
if ($node->parentNode != null && XML_ELEMENT_NODE == $node->parentNode->nodeType) {
$style = self::recursiveParseStylesInHierarchy($node->parentNode, $style);
}
return $style;
}
/**
* Parse list node
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array &$styles
* @param array &$data
*/
private static function parseList($node, $element, &$styles, &$data)
{
$isOrderedList = $node->nodeName === 'ol';
if (isset($data['listdepth'])) {
$data['listdepth']++;
} else {
$data['listdepth'] = 0;
$styles['list'] = 'listStyle_' . self::$listIndex++;
$element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList));
}
if ($node->parentNode->nodeName === 'li') {
return $element->getParent();
}
}
/**
* @param bool $isOrderedList
* @return array
*/
private static function getListStyle($isOrderedList)
{
if ($isOrderedList) {
return array(
'type' => 'multilevel',
'levels' => array(
array('format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360),
array('format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360),
array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180),
array('format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360),
array('format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360),
array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180),
array('format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360),
array('format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360),
array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180),
),
);
}
return array(
'type' => 'hybridMultilevel',
'levels' => array(
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
),
);
}
/**
* Parse list item node
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array &$styles
* @param array $data
*
* @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 (!empty($cNodes)) {
$listRun = $element->addListItemRun($data['listdepth'], $styles['list'], $styles['paragraph']);
foreach ($cNodes as $cNode) {
self::parseNode($cNode, $listRun, $styles, $data);
}
}
}
/**
* 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) = array_pad(explode(':', $property, 2), 2, null);
$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['alignment'] = self::mapAlign($cValue);
break;
case 'direction':
$styles['rtl'] = $cValue === 'rtl';
break;
case 'font-size':
$styles['size'] = Converter::cssToPoint($cValue);
break;
case 'font-family':
$cValue = array_map('trim', explode(',', $cValue));
$styles['name'] = ucwords($cValue[0]);
break;
case 'color':
$styles['color'] = trim($cValue, '#');
break;
case 'background-color':
$styles['bgColor'] = trim($cValue, '#');
break;
case 'line-height':
if (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) {
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT;
$spacing = Converter::cssToTwip($matches[1]) / \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT;
} elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) {
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
$spacing = ((int) $matches[1]) / 100;
} else {
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
$spacing = $cValue;
}
$styles['spacingLineRule'] = $spacingLineRule;
$styles['lineHeight'] = $spacing;
break;
case 'text-indent':
$styles['indentation']['firstLine'] = Converter::cssToTwip($cValue);
break;
case 'font-weight':
$tValue = false;
if (preg_match('#bold#', $cValue)) {
$tValue = true; // also match bolder
}
$styles['bold'] = $tValue;
break;
case 'font-style':
$tValue = false;
if (preg_match('#(?:italic|oblique)#', $cValue)) {
$tValue = true;
}
$styles['italic'] = $tValue;
break;
case 'margin-top':
$styles['spaceBefore'] = Converter::cssToPoint($cValue);
break;
case 'margin-bottom':
$styles['spaceAfter'] = Converter::cssToPoint($cValue);
break;
case 'border-color':
$styles['color'] = trim($cValue, '#');
break;
case 'border-width':
$styles['borderSize'] = Converter::cssToPoint($cValue);
break;
case 'border-style':
$styles['borderStyle'] = self::mapBorderStyle($cValue);
break;
case 'width':
if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) {
$styles['width'] = Converter::cssToTwip($matches[1]);
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP;
} elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) {
$styles['width'] = $matches[1] * 50;
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT;
} elseif (preg_match('/([0-9]+)/', $cValue, $matches)) {
$styles['width'] = $matches[1];
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO;
}
break;
case 'border':
if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+)\s+([a-z]+)/', $cValue, $matches)) {
$styles['borderSize'] = Converter::cssToPoint($matches[1]);
$styles['borderColor'] = trim($matches[2], '#');
$styles['borderStyle'] = self::mapBorderStyle($matches[3]);
}
break;
}
}
return $styles;
}
/**
* Parse image node
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
*
* @return \PhpOffice\PhpWord\Element\Image
**/
private static function parseImage($node, $element)
{
$style = array();
$src = null;
foreach ($node->attributes as $attribute) {
switch ($attribute->name) {
case 'src':
$src = $attribute->value;
break;
case 'width':
$width = $attribute->value;
$style['width'] = $width;
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
break;
case 'height':
$height = $attribute->value;
$style['height'] = $height;
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
break;
case 'style':
$styleattr = explode(';', $attribute->value);
foreach ($styleattr as $attr) {
if (strpos($attr, ':')) {
list($k, $v) = explode(':', $attr);
switch ($k) {
case 'float':
if (trim($v) == 'right') {
$style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_RIGHT;
$style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE;
$style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE;
$style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT;
$style['overlap'] = true;
}
if (trim($v) == 'left') {
$style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_LEFT;
$style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE;
$style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE;
$style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT;
$style['overlap'] = true;
}
break;
}
}
}
break;
}
}
$originSrc = $src;
if (strpos($src, 'data:image') !== false) {
$tmpDir = Settings::getTempDir() . '/';
$match = array();
preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match);
$src = $imgFile = $tmpDir . uniqid() . '.' . $match[1];
$ifp = fopen($imgFile, 'wb');
if ($ifp !== false) {
fwrite($ifp, base64_decode($match[2]));
fclose($ifp);
}
}
$src = urldecode($src);
if (!is_file($src)
&& !is_null(self::$options)
&& isset(self::$options['IMG_SRC_SEARCH'])
&& isset(self::$options['IMG_SRC_REPLACE'])) {
$src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src);
}
if (!is_file($src)) {
if ($imgBlob = @file_get_contents($src)) {
$tmpDir = Settings::getTempDir() . '/';
$match = array();
preg_match('/.+\.(\w+)$/', $src, $match);
$src = $tmpDir . uniqid() . '.' . $match[1];
$ifp = fopen($src, 'wb');
if ($ifp !== false) {
fwrite($ifp, $imgBlob);
fclose($ifp);
}
}
}
if (is_file($src)) {
$newElement = $element->addImage($src, $style);
} else {
throw new \Exception("Could not load image $originSrc");
}
return $newElement;
}
/**
* Transforms a CSS border style into a word border style
*
* @param string $cssBorderStyle
* @return null|string
*/
private static function mapBorderStyle($cssBorderStyle)
{
switch ($cssBorderStyle) {
case 'none':
case 'dashed':
case 'dotted':
case 'double':
return $cssBorderStyle;
default:
return 'single';
}
}
/**
* Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc
*
* @param string $cssAlignment
* @return string|null
*/
private static function mapAlign($cssAlignment)
{
switch ($cssAlignment) {
case 'right':
return Jc::END;
case 'center':
return Jc::CENTER;
case 'justify':
return Jc::BOTH;
default:
return Jc::START;
}
return null;
}
/**
* Parse line break
*
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
*/
private static function parseLineBreak($element)
{
$element->addTextBreak();
}
/**
* Parse link node
*
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array $styles
*/
private static function parseLink($node, $element, &$styles)
{
$target = null;
foreach ($node->attributes as $attribute) {
switch ($attribute->name) {
case 'href':
$target = $attribute->value;
break;
}
}
$styles['font'] = self::parseInlineStyle($node, $styles['font']);
if (strpos($target, '#') === 0) {
return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true);
}
return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']);
}
}