暂无描述

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. /**
  3. * This file is part of PHPWord - A pure PHP library for reading and writing
  4. * word processing documents.
  5. *
  6. * PHPWord is free software distributed under the terms of the GNU Lesser
  7. * General Public License version 3 as published by the Free Software Foundation.
  8. *
  9. * For the full copyright and license information, please read the LICENSE
  10. * file that was distributed with this source code. For the full list of
  11. * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
  12. *
  13. * @link https://github.com/PHPOffice/PHPWord
  14. * @copyright 2010-2014 PHPWord contributors
  15. * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
  16. */
  17. namespace PhpOffice\PhpWord\Shared;
  18. use PhpOffice\PhpWord\Element\AbstractContainer;
  19. /**
  20. * Common Html functions
  21. *
  22. * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode
  23. */
  24. class Html
  25. {
  26. /**
  27. * Add HTML parts.
  28. *
  29. * Note: $stylesheet parameter is removed to avoid PHPMD error for unused parameter
  30. *
  31. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added
  32. * @param string $html The code to parse
  33. * @param bool $fullHTML If it's a full HTML, no need to add 'body' tag
  34. * @return void
  35. */
  36. public static function addHtml($element, $html, $fullHTML = false)
  37. {
  38. /*
  39. * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element,
  40. * which could be applied when such an element occurs in the parseNode function.
  41. */
  42. // Preprocess: remove all line ends, decode HTML entity,
  43. // fix ampersand and angle brackets and add body tag for HTML fragments
  44. $html = str_replace(array("\n", "\r"), '', $html);
  45. $html = str_replace(array('&lt;', '&gt;', '&amp;'), array('_lt_', '_gt_', '_amp_'), $html);
  46. $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8');
  47. $html = str_replace('&', '&amp;', $html);
  48. $html = str_replace(array('_lt_', '_gt_', '_amp_'), array('&lt;', '&gt;', '&amp;'), $html);
  49. if ($fullHTML === false) {
  50. $html = '<body>' . $html . '</body>';
  51. }
  52. // Load DOM
  53. $dom = new \DOMDocument();
  54. $dom->preserveWhiteSpace = true;
  55. $dom->loadXML($html);
  56. $node = $dom->getElementsByTagName('body');
  57. self::parseNode($node->item(0), $element);
  58. }
  59. /**
  60. * parse Inline style of a node
  61. *
  62. * @param \DOMNode $node Node to check on attributes and to compile a style array
  63. * @param array $styles is supplied, the inline style attributes are added to the already existing style
  64. * @return array
  65. */
  66. protected static function parseInlineStyle($node, $styles = array())
  67. {
  68. if ($node->nodeType == XML_ELEMENT_NODE) {
  69. $attributes = $node->attributes; // get all the attributes(eg: id, class)
  70. foreach ($attributes as $attribute) {
  71. switch ($attribute->name) {
  72. case 'style':
  73. $styles = self::parseStyle($attribute, $styles);
  74. break;
  75. }
  76. }
  77. }
  78. return $styles;
  79. }
  80. /**
  81. * Parse a node and add a corresponding element to the parent element.
  82. *
  83. * @param \DOMNode $node node to parse
  84. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node
  85. * @param array $styles Array with all styles
  86. * @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems
  87. * @return void
  88. */
  89. protected static function parseNode($node, $element, $styles = array(), $data = array())
  90. {
  91. // Populate styles array
  92. $styleTypes = array('font', 'paragraph', 'list');
  93. foreach ($styleTypes as $styleType) {
  94. if (!isset($styles[$styleType])) {
  95. $styles[$styleType] = array();
  96. }
  97. }
  98. // Node mapping table
  99. $nodes = array(
  100. // $method $node $element $styles $data $argument1 $argument2
  101. 'p' => array('Paragraph', $node, $element, $styles, null, null, null),
  102. 'h1' => array('Heading', null, $element, $styles, null, 'Heading1', null),
  103. 'h2' => array('Heading', null, $element, $styles, null, 'Heading2', null),
  104. 'h3' => array('Heading', null, $element, $styles, null, 'Heading3', null),
  105. 'h4' => array('Heading', null, $element, $styles, null, 'Heading4', null),
  106. 'h5' => array('Heading', null, $element, $styles, null, 'Heading5', null),
  107. 'h6' => array('Heading', null, $element, $styles, null, 'Heading6', null),
  108. '#text' => array('Text', $node, $element, $styles, null, null, null),
  109. 'strong' => array('Property', null, null, $styles, null, 'bold', true),
  110. 'em' => array('Property', null, null, $styles, null, 'italic', true),
  111. 'sup' => array('Property', null, null, $styles, null, 'superScript', true),
  112. 'sub' => array('Property', null, null, $styles, null, 'subScript', true),
  113. 'table' => array('Table', $node, $element, $styles, null, 'addTable', true),
  114. 'tr' => array('Table', $node, $element, $styles, null, 'addRow', true),
  115. 'td' => array('Table', $node, $element, $styles, null, 'addCell', true),
  116. 'ul' => array('List', null, null, $styles, $data, 3, null),
  117. 'ol' => array('List', null, null, $styles, $data, 7, null),
  118. 'li' => array('ListItem', $node, $element, $styles, $data, null, null),
  119. );
  120. $newElement = null;
  121. $keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2');
  122. if (isset($nodes[$node->nodeName])) {
  123. // Execute method based on node mapping table and return $newElement or null
  124. // Arguments are passed by reference
  125. $arguments = array();
  126. $args = array();
  127. list($method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) = $nodes[$node->nodeName];
  128. for ($i = 0; $i <= 5; $i++) {
  129. if ($args[$i] !== null) {
  130. $arguments[$keys[$i]] = &$args[$i];
  131. }
  132. }
  133. $method = "parse{$method}";
  134. $newElement = call_user_func_array(array('PhpOffice\PhpWord\Shared\Html', $method), $arguments);
  135. // Retrieve back variables from arguments
  136. foreach ($keys as $key) {
  137. if (array_key_exists($key, $arguments)) {
  138. $$key = $arguments[$key];
  139. }
  140. }
  141. }
  142. if ($newElement === null) {
  143. $newElement = $element;
  144. }
  145. self::parseChildNodes($node, $newElement, $styles, $data);
  146. }
  147. /**
  148. * Parse child nodes.
  149. *
  150. * @param \DOMNode $node
  151. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  152. * @param array $styles
  153. * @param array $data
  154. * @return void
  155. */
  156. private static function parseChildNodes($node, $element, $styles, $data)
  157. {
  158. if ($node->nodeName != 'li') {
  159. $cNodes = $node->childNodes;
  160. if (count($cNodes) > 0) {
  161. foreach ($cNodes as $cNode) {
  162. if ($element instanceof AbstractContainer) {
  163. self::parseNode($cNode, $element, $styles, $data);
  164. }
  165. }
  166. }
  167. }
  168. }
  169. /**
  170. * Parse paragraph node
  171. *
  172. * @param \DOMNode $node
  173. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  174. * @param array &$styles
  175. * @return \PhpOffice\PhpWord\Element\TextRun
  176. */
  177. private static function parseParagraph($node, $element, &$styles)
  178. {
  179. $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']);
  180. $newElement = $element->addTextRun($styles['paragraph']);
  181. return $newElement;
  182. }
  183. /**
  184. * Parse heading node
  185. *
  186. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  187. * @param array &$styles
  188. * @param string $argument1 Name of heading style
  189. * @return \PhpOffice\PhpWord\Element\TextRun
  190. *
  191. * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that
  192. * Heading1 - Heading6 are already defined somewhere
  193. */
  194. private static function parseHeading($element, &$styles, $argument1)
  195. {
  196. $styles['paragraph'] = $argument1;
  197. $newElement = $element->addTextRun($styles['paragraph']);
  198. return $newElement;
  199. }
  200. /**
  201. * Parse text node
  202. *
  203. * @param \DOMNode $node
  204. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  205. * @param array &$styles
  206. * @return null
  207. */
  208. private static function parseText($node, $element, &$styles)
  209. {
  210. $styles['font'] = self::parseInlineStyle($node, $styles['font']);
  211. // Commented as source of bug #257. `method_exists` doesn't seems to work properly in this case.
  212. // @todo Find better error checking for this one
  213. // if (method_exists($element, 'addText')) {
  214. $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']);
  215. // }
  216. return null;
  217. }
  218. /**
  219. * Parse property node
  220. *
  221. * @param array &$styles
  222. * @param string $argument1 Style name
  223. * @param string $argument2 Style value
  224. * @return null
  225. */
  226. private static function parseProperty(&$styles, $argument1, $argument2)
  227. {
  228. $styles['font'][$argument1] = $argument2;
  229. return null;
  230. }
  231. /**
  232. * Parse table node
  233. *
  234. * @param \DOMNode $node
  235. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  236. * @param array &$styles
  237. * @param string $argument1 Method name
  238. * @return \PhpOffice\PhpWord\Element\AbstractContainer $element
  239. *
  240. * @todo As soon as TableItem, RowItem and CellItem support relative width and height
  241. */
  242. private static function parseTable($node, $element, &$styles, $argument1)
  243. {
  244. $styles['paragraph'] = self::parseInlineStyle($node, $styles['paragraph']);
  245. $newElement = $element->$argument1();
  246. // $attributes = $node->attributes;
  247. // if ($attributes->getNamedItem('width') !== null) {
  248. // $newElement->setWidth($attributes->getNamedItem('width')->value);
  249. // }
  250. // if ($attributes->getNamedItem('height') !== null) {
  251. // $newElement->setHeight($attributes->getNamedItem('height')->value);
  252. // }
  253. // if ($attributes->getNamedItem('width') !== null) {
  254. // $newElement=$element->addCell($width=$attributes->getNamedItem('width')->value);
  255. // }
  256. return $newElement;
  257. }
  258. /**
  259. * Parse list node
  260. *
  261. * @param array &$styles
  262. * @param array &$data
  263. * @param string $argument1 List type
  264. * @return null
  265. */
  266. private static function parseList(&$styles, &$data, $argument1)
  267. {
  268. if (isset($data['listdepth'])) {
  269. $data['listdepth']++;
  270. } else {
  271. $data['listdepth'] = 0;
  272. }
  273. $styles['list']['listType'] = $argument1;
  274. return null;
  275. }
  276. /**
  277. * Parse list item node
  278. *
  279. * @param \DOMNode $node
  280. * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
  281. * @param array &$styles
  282. * @param array $data
  283. * @return null
  284. *
  285. * @todo This function is almost the same like `parseChildNodes`. Merged?
  286. * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes
  287. */
  288. private static function parseListItem($node, $element, &$styles, $data)
  289. {
  290. $cNodes = $node->childNodes;
  291. if (count($cNodes) > 0) {
  292. $text = '';
  293. foreach ($cNodes as $cNode) {
  294. if ($cNode->nodeName == '#text') {
  295. $text = $cNode->nodeValue;
  296. }
  297. }
  298. $element->addListItem($text, $data['listdepth'], $styles['font'], $styles['list'], $styles['paragraph']);
  299. }
  300. return null;
  301. }
  302. /**
  303. * Parse style
  304. *
  305. * @param \DOMAttr $attribute
  306. * @param array $styles
  307. * @return array
  308. */
  309. private static function parseStyle($attribute, $styles)
  310. {
  311. $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;"));
  312. foreach ($properties as $property) {
  313. list($cKey, $cValue) = explode(':', $property, 2);
  314. $cValue = trim($cValue);
  315. switch (trim($cKey)) {
  316. case 'text-decoration':
  317. switch ($cValue) {
  318. case 'underline':
  319. $styles['underline'] = 'single';
  320. break;
  321. case 'line-through':
  322. $styles['strikethrough'] = true;
  323. break;
  324. }
  325. break;
  326. case 'text-align':
  327. $styles['align'] = $cValue;
  328. break;
  329. case 'color':
  330. $styles['color'] = trim($cValue, "#");
  331. break;
  332. case 'background-color':
  333. $styles['bgColor'] = trim($cValue, "#");
  334. break;
  335. }
  336. }
  337. return $styles;
  338. }
  339. }