Content.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. * @see https://github.com/PHPOffice/PHPWord
  14. * @copyright 2010-2018 PHPWord contributors
  15. * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
  16. */
  17. namespace PhpOffice\PhpWord\Writer\ODText\Part;
  18. use PhpOffice\PhpWord\Element\AbstractContainer;
  19. use PhpOffice\PhpWord\Element\Field;
  20. use PhpOffice\PhpWord\Element\Image;
  21. use PhpOffice\PhpWord\Element\Table;
  22. use PhpOffice\PhpWord\Element\Text;
  23. use PhpOffice\PhpWord\Element\TextRun;
  24. use PhpOffice\PhpWord\Element\TrackChange;
  25. use PhpOffice\PhpWord\PhpWord;
  26. use PhpOffice\PhpWord\Shared\XMLWriter;
  27. use PhpOffice\PhpWord\Style;
  28. use PhpOffice\PhpWord\Style\Font;
  29. use PhpOffice\PhpWord\Style\Paragraph;
  30. use PhpOffice\PhpWord\Style\Table as TableStyle;
  31. use PhpOffice\PhpWord\Writer\ODText\Element\Container;
  32. use PhpOffice\PhpWord\Writer\ODText\Style\Paragraph as ParagraphStyleWriter;
  33. /**
  34. * ODText content part writer: content.xml
  35. */
  36. class Content extends AbstractPart
  37. {
  38. /**
  39. * Auto style collection
  40. *
  41. * Collect inline style information from section, image, and table elements
  42. *
  43. * @todo Merge font and paragraph styles
  44. * @var array
  45. */
  46. private $autoStyles = array('Section' => array(), 'Image' => array(), 'Table' => array());
  47. private $imageParagraphStyles = array();
  48. /**
  49. * Write part
  50. *
  51. * @return string
  52. */
  53. public function write()
  54. {
  55. $xmlWriter = $this->getXmlWriter();
  56. $phpWord = $this->getParentWriter()->getPhpWord();
  57. $this->getAutoStyles($phpWord);
  58. $xmlWriter->startDocument('1.0', 'UTF-8');
  59. $xmlWriter->startElement('office:document-content');
  60. $this->writeCommonRootAttributes($xmlWriter);
  61. $xmlWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
  62. $xmlWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
  63. $xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  64. $xmlWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
  65. $xmlWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
  66. // Font declarations and automatic styles
  67. $this->writeFontFaces($xmlWriter); // office:font-face-decls
  68. $this->writeAutoStyles($xmlWriter); // office:automatic-styles
  69. // Body
  70. $xmlWriter->startElement('office:body');
  71. $xmlWriter->startElement('office:text');
  72. // Tracked changes declarations
  73. $trackedChanges = array();
  74. $sections = $phpWord->getSections();
  75. foreach ($sections as $section) {
  76. $this->collectTrackedChanges($section, $trackedChanges);
  77. }
  78. $xmlWriter->startElement('text:tracked-changes');
  79. foreach ($trackedChanges as $trackedElement) {
  80. $trackedChange = $trackedElement->getTrackChange();
  81. $xmlWriter->startElement('text:changed-region');
  82. $trackedChange->setElementId();
  83. $xmlWriter->writeAttribute('text:id', $trackedChange->getElementId());
  84. if (($trackedChange->getChangeType() == TrackChange::INSERTED)) {
  85. $xmlWriter->startElement('text:insertion');
  86. } elseif ($trackedChange->getChangeType() == TrackChange::DELETED) {
  87. $xmlWriter->startElement('text:deletion');
  88. }
  89. $xmlWriter->startElement('office:change-info');
  90. $xmlWriter->writeElement('dc:creator', $trackedChange->getAuthor());
  91. if ($trackedChange->getDate() != null) {
  92. $xmlWriter->writeElement('dc:date', $trackedChange->getDate()->format('Y-m-d\TH:i:s\Z'));
  93. }
  94. $xmlWriter->endElement(); // office:change-info
  95. if ($trackedChange->getChangeType() == TrackChange::DELETED) {
  96. $xmlWriter->writeElement('text:p', $trackedElement->getText());
  97. }
  98. $xmlWriter->endElement(); // text:insertion|text:deletion
  99. $xmlWriter->endElement(); // text:changed-region
  100. }
  101. $xmlWriter->endElement(); // text:tracked-changes
  102. // Sequence declarations
  103. $sequences = array('Illustration', 'Table', 'Text', 'Drawing');
  104. $xmlWriter->startElement('text:sequence-decls');
  105. foreach ($sequences as $sequence) {
  106. $xmlWriter->startElement('text:sequence-decl');
  107. $xmlWriter->writeAttribute('text:display-outline-level', 0);
  108. $xmlWriter->writeAttribute('text:name', $sequence);
  109. $xmlWriter->endElement();
  110. }
  111. $xmlWriter->endElement(); // text:sequence-decl
  112. // Sections
  113. $sections = $phpWord->getSections();
  114. foreach ($sections as $section) {
  115. $name = 'Section' . $section->getSectionId();
  116. $xmlWriter->startElement('text:section');
  117. $xmlWriter->writeAttribute('text:name', $name);
  118. $xmlWriter->writeAttribute('text:style-name', $name);
  119. $xmlWriter->startElement('text:p');
  120. $xmlWriter->writeAttribute('text:style-name', 'SB' . $section->getSectionId());
  121. $xmlWriter->endElement();
  122. $containerWriter = new Container($xmlWriter, $section);
  123. $containerWriter->write();
  124. $xmlWriter->endElement(); // text:section
  125. }
  126. $xmlWriter->endElement(); // office:text
  127. $xmlWriter->endElement(); // office:body
  128. $xmlWriter->endElement(); // office:document-content
  129. return $xmlWriter->getData();
  130. }
  131. /**
  132. * Write automatic styles other than fonts and paragraphs.
  133. *
  134. * @since 0.11.0
  135. *
  136. * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter
  137. */
  138. private function writeAutoStyles(XMLWriter $xmlWriter)
  139. {
  140. $xmlWriter->startElement('office:automatic-styles');
  141. $this->writeTextStyles($xmlWriter);
  142. foreach ($this->autoStyles as $element => $styles) {
  143. $writerClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Style\\' . $element;
  144. foreach ($styles as $style) {
  145. /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */
  146. $styleWriter = new $writerClass($xmlWriter, $style);
  147. $styleWriter->write();
  148. }
  149. }
  150. $xmlWriter->endElement(); // office:automatic-styles
  151. }
  152. /**
  153. * Write automatic styles.
  154. *
  155. * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter
  156. */
  157. private function writeTextStyles(XMLWriter $xmlWriter)
  158. {
  159. $styles = Style::getStyles();
  160. $paragraphStyleCount = 0;
  161. $style = new Paragraph();
  162. $style->setStyleName('PB');
  163. $style->setAuto();
  164. $styleWriter = new ParagraphStyleWriter($xmlWriter, $style);
  165. $styleWriter->write();
  166. $sects = $this->getParentWriter()->getPhpWord()->getSections();
  167. $countsects = count($sects);
  168. for ($i = 0; $i < $countsects; ++$i) {
  169. $iplus1 = $i + 1;
  170. $style = new Paragraph();
  171. $style->setStyleName("SB$iplus1");
  172. $style->setAuto();
  173. $pnstart = $sects[$i]->getStyle()->getPageNumberingStart();
  174. $style->setNumLevel($pnstart);
  175. $styleWriter = new ParagraphStyleWriter($xmlWriter, $style);
  176. $styleWriter->write();
  177. }
  178. foreach ($styles as $style) {
  179. $sty = $style->getStyleName();
  180. if (substr($sty, 0, 8) === 'Heading_') {
  181. $style = new Paragraph();
  182. $style->setStyleName('HD' . substr($sty, 8));
  183. $style->setAuto();
  184. $styleWriter = new ParagraphStyleWriter($xmlWriter, $style);
  185. $styleWriter->write();
  186. $style = new Paragraph();
  187. $style->setStyleName('HE' . substr($sty, 8));
  188. $style->setAuto();
  189. $styleWriter = new ParagraphStyleWriter($xmlWriter, $style);
  190. $styleWriter->write();
  191. }
  192. }
  193. foreach ($styles as $style) {
  194. if ($style->isAuto() === true) {
  195. $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style));
  196. if (class_exists($styleClass)) {
  197. /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */
  198. $styleWriter = new $styleClass($xmlWriter, $style);
  199. $styleWriter->write();
  200. }
  201. if ($style instanceof Paragraph) {
  202. $paragraphStyleCount++;
  203. }
  204. }
  205. }
  206. foreach ($this->imageParagraphStyles as $style) {
  207. $styleWriter = new \PhpOffice\PhpWord\Writer\ODText\Style\Paragraph($xmlWriter, $style);
  208. $styleWriter->write();
  209. }
  210. }
  211. /**
  212. * Get automatic styles.
  213. *
  214. * @param \PhpOffice\PhpWord\PhpWord $phpWord
  215. */
  216. private function getAutoStyles(PhpWord $phpWord)
  217. {
  218. $sections = $phpWord->getSections();
  219. $paragraphStyleCount = 0;
  220. $fontStyleCount = 0;
  221. foreach ($sections as $section) {
  222. $style = $section->getStyle();
  223. $style->setStyleName("Section{$section->getSectionId()}");
  224. $this->autoStyles['Section'][] = $style;
  225. $this->getContainerStyle($section, $paragraphStyleCount, $fontStyleCount);
  226. }
  227. }
  228. /**
  229. * Get all styles of each elements in container recursively
  230. *
  231. * Table style can be null or string of the style name
  232. *
  233. * @param \PhpOffice\PhpWord\Element\AbstractContainer $container
  234. * @param int $paragraphStyleCount
  235. * @param int $fontStyleCount
  236. * @todo Simplify the logic
  237. */
  238. private function getContainerStyle($container, &$paragraphStyleCount, &$fontStyleCount)
  239. {
  240. $elements = $container->getElements();
  241. foreach ($elements as $element) {
  242. if ($element instanceof TextRun) {
  243. $this->getElementStyleTextRun($element, $paragraphStyleCount);
  244. $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount);
  245. } elseif ($element instanceof Text) {
  246. $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount);
  247. } elseif ($element instanceof Field) {
  248. $this->getElementStyleField($element, $fontStyleCount);
  249. } elseif ($element instanceof Image) {
  250. $style = $element->getStyle();
  251. $style->setStyleName('fr' . $element->getMediaIndex());
  252. $this->autoStyles['Image'][] = $style;
  253. $sty = new \PhpOffice\PhpWord\Style\Paragraph();
  254. $sty->setStyleName('IM' . $element->getMediaIndex());
  255. $sty->setAuto();
  256. $sty->setAlignment($style->getAlignment());
  257. $this->imageParagraphStyles[] = $sty;
  258. } elseif ($element instanceof Table) {
  259. /** @var \PhpOffice\PhpWord\Style\Table $style */
  260. $style = $element->getStyle();
  261. if (is_string($style)) {
  262. $style = Style::getStyle($style);
  263. }
  264. if ($style === null) {
  265. $style = new TableStyle();
  266. }
  267. $style->setStyleName($element->getElementId());
  268. $style->setColumnWidths($element->findFirstDefinedCellWidths());
  269. $this->autoStyles['Table'][] = $style;
  270. }
  271. }
  272. }
  273. /**
  274. * Get style of individual element
  275. *
  276. * @param \PhpOffice\PhpWord\Element\Text $element
  277. * @param int $paragraphStyleCount
  278. * @param int $fontStyleCount
  279. */
  280. private function getElementStyle($element, &$paragraphStyleCount, &$fontStyleCount)
  281. {
  282. $fontStyle = $element->getFontStyle();
  283. $paragraphStyle = $element->getParagraphStyle();
  284. $phpWord = $this->getParentWriter()->getPhpWord();
  285. if ($fontStyle instanceof Font) {
  286. // Font
  287. $name = $fontStyle->getStyleName();
  288. if (!$name) {
  289. $fontStyleCount++;
  290. $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null);
  291. $style->setAuto();
  292. $style->setParagraph(null);
  293. $element->setFontStyle("T{$fontStyleCount}");
  294. } else {
  295. $element->setFontStyle($name);
  296. }
  297. }
  298. if ($paragraphStyle instanceof Paragraph) {
  299. // Paragraph
  300. $name = $paragraphStyle->getStyleName();
  301. if (!$name) {
  302. $paragraphStyleCount++;
  303. $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle);
  304. $style->setAuto();
  305. $element->setParagraphStyle("P{$paragraphStyleCount}");
  306. } else {
  307. $element->setParagraphStyle($name);
  308. }
  309. } elseif ($paragraphStyle) {
  310. $paragraphStyleCount++;
  311. $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle";
  312. $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle);
  313. $style->setAuto();
  314. $element->setParagraphStyle($parstylename);
  315. }
  316. }
  317. /**
  318. * Get font style of individual field element
  319. *
  320. * @param \PhpOffice\PhpWord\Element\Field $element
  321. * @param int $paragraphStyleCount
  322. * @param int $fontStyleCount
  323. */
  324. private function getElementStyleField($element, &$fontStyleCount)
  325. {
  326. $fontStyle = $element->getFontStyle();
  327. $phpWord = $this->getParentWriter()->getPhpWord();
  328. if ($fontStyle instanceof Font) {
  329. $name = $fontStyle->getStyleName();
  330. if (!$name) {
  331. $fontStyleCount++;
  332. $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle, null);
  333. $style->setAuto();
  334. $style->setParagraph(null);
  335. $element->setFontStyle("T{$fontStyleCount}");
  336. } else {
  337. $element->setFontStyle($name);
  338. }
  339. }
  340. }
  341. /**
  342. * Get style of individual element
  343. *
  344. * @param \PhpOffice\PhpWord\Element\TextRun $element
  345. * @param int $paragraphStyleCount
  346. */
  347. private function getElementStyleTextRun($element, &$paragraphStyleCount)
  348. {
  349. $paragraphStyle = $element->getParagraphStyle();
  350. $phpWord = $this->getParentWriter()->getPhpWord();
  351. if ($paragraphStyle instanceof Paragraph) {
  352. // Paragraph
  353. $name = $paragraphStyle->getStyleName();
  354. if (!$name) {
  355. $paragraphStyleCount++;
  356. $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", $paragraphStyle);
  357. $style->setAuto();
  358. $element->setParagraphStyle("P{$paragraphStyleCount}");
  359. } else {
  360. $element->setParagraphStyle($name);
  361. }
  362. } elseif ($paragraphStyle) {
  363. $paragraphStyleCount++;
  364. $parstylename = "P$paragraphStyleCount" . "_$paragraphStyle";
  365. $style = $phpWord->addParagraphStyle($parstylename, $paragraphStyle);
  366. $style->setAuto();
  367. $element->setParagraphStyle($parstylename);
  368. }
  369. }
  370. /**
  371. * Finds all tracked changes
  372. *
  373. * @param AbstractContainer $container
  374. * @param \PhpOffice\PhpWord\Element\AbstractElement[] $trackedChanges
  375. */
  376. private function collectTrackedChanges(AbstractContainer $container, &$trackedChanges = array())
  377. {
  378. $elements = $container->getElements();
  379. foreach ($elements as $element) {
  380. if ($element->getTrackChange() != null) {
  381. $trackedChanges[] = $element;
  382. }
  383. if (is_callable(array($element, 'getElements'))) {
  384. $this->collectTrackedChanges($element, $trackedChanges);
  385. }
  386. }
  387. }
  388. }