StringTable.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  3. use PhpOffice\PhpSpreadsheet\Cell\Cell;
  4. use PhpOffice\PhpSpreadsheet\Cell\DataType;
  5. use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
  6. use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
  7. use PhpOffice\PhpSpreadsheet\RichText\RichText;
  8. use PhpOffice\PhpSpreadsheet\RichText\Run;
  9. use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  10. use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  11. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as ActualWorksheet;
  12. class StringTable extends WriterPart
  13. {
  14. /**
  15. * Create worksheet stringtable.
  16. *
  17. * @param string[] $existingTable Existing table to eventually merge with
  18. *
  19. * @return string[] String table for worksheet
  20. */
  21. public function createStringTable(ActualWorksheet $worksheet, $existingTable = null)
  22. {
  23. // Create string lookup table
  24. $aStringTable = [];
  25. // Is an existing table given?
  26. if (($existingTable !== null) && is_array($existingTable)) {
  27. $aStringTable = $existingTable;
  28. }
  29. // Fill index array
  30. $aFlippedStringTable = $this->flipStringTable($aStringTable);
  31. // Loop through cells
  32. foreach ($worksheet->getCellCollection()->getCoordinates() as $coordinate) {
  33. /** @var Cell $cell */
  34. $cell = $worksheet->getCellCollection()->get($coordinate);
  35. $cellValue = $cell->getValue();
  36. if (
  37. !is_object($cellValue) &&
  38. ($cellValue !== null) &&
  39. $cellValue !== '' &&
  40. ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL) &&
  41. !isset($aFlippedStringTable[$cellValue])
  42. ) {
  43. $aStringTable[] = $cellValue;
  44. $aFlippedStringTable[$cellValue] = true;
  45. } elseif (
  46. $cellValue instanceof RichText &&
  47. ($cellValue !== null) &&
  48. !isset($aFlippedStringTable[$cellValue->getHashCode()])
  49. ) {
  50. $aStringTable[] = $cellValue;
  51. $aFlippedStringTable[$cellValue->getHashCode()] = true;
  52. }
  53. }
  54. return $aStringTable;
  55. }
  56. /**
  57. * Write string table to XML format.
  58. *
  59. * @param (RichText|string)[] $stringTable
  60. *
  61. * @return string XML Output
  62. */
  63. public function writeStringTable(array $stringTable)
  64. {
  65. // Create XML writer
  66. $objWriter = null;
  67. if ($this->getParentWriter()->getUseDiskCaching()) {
  68. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  69. } else {
  70. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  71. }
  72. // XML header
  73. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  74. // String table
  75. $objWriter->startElement('sst');
  76. $objWriter->writeAttribute('xmlns', Namespaces::MAIN);
  77. $objWriter->writeAttribute('uniqueCount', (string) count($stringTable));
  78. // Loop through string table
  79. foreach ($stringTable as $textElement) {
  80. $objWriter->startElement('si');
  81. if (!($textElement instanceof RichText)) {
  82. $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
  83. $objWriter->startElement('t');
  84. if ($textToWrite !== trim($textToWrite)) {
  85. $objWriter->writeAttribute('xml:space', 'preserve');
  86. }
  87. $objWriter->writeRawData($textToWrite);
  88. $objWriter->endElement();
  89. } else {
  90. $this->writeRichText($objWriter, $textElement);
  91. }
  92. $objWriter->endElement();
  93. }
  94. $objWriter->endElement();
  95. return $objWriter->getData();
  96. }
  97. /**
  98. * Write Rich Text.
  99. *
  100. * @param string $prefix Optional Namespace prefix
  101. */
  102. public function writeRichText(XMLWriter $objWriter, RichText $richText, $prefix = null): void
  103. {
  104. if ($prefix !== null) {
  105. $prefix .= ':';
  106. }
  107. // Loop through rich text elements
  108. $elements = $richText->getRichTextElements();
  109. foreach ($elements as $element) {
  110. // r
  111. $objWriter->startElement($prefix . 'r');
  112. // rPr
  113. if ($element instanceof Run && $element->getFont() !== null) {
  114. // rPr
  115. $objWriter->startElement($prefix . 'rPr');
  116. // rFont
  117. if ($element->getFont()->getName() !== null) {
  118. $objWriter->startElement($prefix . 'rFont');
  119. $objWriter->writeAttribute('val', $element->getFont()->getName());
  120. $objWriter->endElement();
  121. }
  122. // Bold
  123. $objWriter->startElement($prefix . 'b');
  124. $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
  125. $objWriter->endElement();
  126. // Italic
  127. $objWriter->startElement($prefix . 'i');
  128. $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
  129. $objWriter->endElement();
  130. // Superscript / subscript
  131. if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
  132. $objWriter->startElement($prefix . 'vertAlign');
  133. if ($element->getFont()->getSuperscript()) {
  134. $objWriter->writeAttribute('val', 'superscript');
  135. } elseif ($element->getFont()->getSubscript()) {
  136. $objWriter->writeAttribute('val', 'subscript');
  137. }
  138. $objWriter->endElement();
  139. }
  140. // Strikethrough
  141. $objWriter->startElement($prefix . 'strike');
  142. $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
  143. $objWriter->endElement();
  144. // Color
  145. if ($element->getFont()->getColor()->getARGB() !== null) {
  146. $objWriter->startElement($prefix . 'color');
  147. $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
  148. $objWriter->endElement();
  149. }
  150. // Size
  151. if ($element->getFont()->getSize() !== null) {
  152. $objWriter->startElement($prefix . 'sz');
  153. $objWriter->writeAttribute('val', (string) $element->getFont()->getSize());
  154. $objWriter->endElement();
  155. }
  156. // Underline
  157. if ($element->getFont()->getUnderline() !== null) {
  158. $objWriter->startElement($prefix . 'u');
  159. $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
  160. $objWriter->endElement();
  161. }
  162. $objWriter->endElement();
  163. }
  164. // t
  165. $objWriter->startElement($prefix . 't');
  166. $objWriter->writeAttribute('xml:space', 'preserve');
  167. $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
  168. $objWriter->endElement();
  169. $objWriter->endElement();
  170. }
  171. }
  172. /**
  173. * Write Rich Text.
  174. *
  175. * @param RichText|string $richText text string or Rich text
  176. * @param string $prefix Optional Namespace prefix
  177. */
  178. public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = ''): void
  179. {
  180. if (!($richText instanceof RichText)) {
  181. $textRun = $richText;
  182. $richText = new RichText();
  183. $run = $richText->createTextRun($textRun ?? '');
  184. $run->setFont(null);
  185. }
  186. if ($prefix !== '') {
  187. $prefix .= ':';
  188. }
  189. // Loop through rich text elements
  190. $elements = $richText->getRichTextElements();
  191. foreach ($elements as $element) {
  192. // r
  193. $objWriter->startElement($prefix . 'r');
  194. if ($element->getFont() !== null) {
  195. // rPr
  196. $objWriter->startElement($prefix . 'rPr');
  197. $fontSize = $element->getFont()->getSize();
  198. if (is_numeric($fontSize)) {
  199. $fontSize *= (($fontSize < 100) ? 100 : 1);
  200. $objWriter->writeAttribute('sz', (string) $fontSize);
  201. }
  202. // Bold
  203. $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? '1' : '0'));
  204. // Italic
  205. $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? '1' : '0'));
  206. // Underline
  207. $underlineType = $element->getFont()->getUnderline();
  208. switch ($underlineType) {
  209. case 'single':
  210. $underlineType = 'sng';
  211. break;
  212. case 'double':
  213. $underlineType = 'dbl';
  214. break;
  215. }
  216. if ($underlineType !== null) {
  217. $objWriter->writeAttribute('u', $underlineType);
  218. }
  219. // Strikethrough
  220. $objWriter->writeAttribute('strike', ($element->getFont()->getStriketype() ?: 'noStrike'));
  221. // Superscript/subscript
  222. if ($element->getFont()->getBaseLine()) {
  223. $objWriter->writeAttribute('baseline', (string) $element->getFont()->getBaseLine());
  224. }
  225. // Color
  226. $this->writeChartTextColor($objWriter, $element->getFont()->getChartColor(), $prefix);
  227. // Underscore Color
  228. $this->writeChartTextColor($objWriter, $element->getFont()->getUnderlineColor(), $prefix, 'uFill');
  229. // fontName
  230. if ($element->getFont()->getLatin()) {
  231. $objWriter->startElement($prefix . 'latin');
  232. $objWriter->writeAttribute('typeface', $element->getFont()->getLatin());
  233. $objWriter->endElement();
  234. }
  235. if ($element->getFont()->getEastAsian()) {
  236. $objWriter->startElement($prefix . 'ea');
  237. $objWriter->writeAttribute('typeface', $element->getFont()->getEastAsian());
  238. $objWriter->endElement();
  239. }
  240. if ($element->getFont()->getComplexScript()) {
  241. $objWriter->startElement($prefix . 'cs');
  242. $objWriter->writeAttribute('typeface', $element->getFont()->getComplexScript());
  243. $objWriter->endElement();
  244. }
  245. $objWriter->endElement();
  246. }
  247. // t
  248. $objWriter->startElement($prefix . 't');
  249. $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
  250. $objWriter->endElement();
  251. $objWriter->endElement();
  252. }
  253. }
  254. private function writeChartTextColor(XMLWriter $objWriter, ?ChartColor $underlineColor, string $prefix, ?string $openTag = ''): void
  255. {
  256. if ($underlineColor !== null) {
  257. $type = $underlineColor->getType();
  258. $value = $underlineColor->getValue();
  259. if (!empty($type) && !empty($value)) {
  260. if ($openTag !== '') {
  261. $objWriter->startElement($prefix . $openTag);
  262. }
  263. $objWriter->startElement($prefix . 'solidFill');
  264. $objWriter->startElement($prefix . $type);
  265. $objWriter->writeAttribute('val', $value);
  266. $alpha = $underlineColor->getAlpha();
  267. if (is_numeric($alpha)) {
  268. $objWriter->startElement('a:alpha');
  269. $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
  270. $objWriter->endElement();
  271. }
  272. $objWriter->endElement(); // srgbClr/schemeClr/prstClr
  273. $objWriter->endElement(); // solidFill
  274. if ($openTag !== '') {
  275. $objWriter->endElement(); // uFill
  276. }
  277. }
  278. }
  279. }
  280. /**
  281. * Flip string table (for index searching).
  282. *
  283. * @param array $stringTable Stringtable
  284. *
  285. * @return array
  286. */
  287. public function flipStringTable(array $stringTable)
  288. {
  289. // Return value
  290. $returnValue = [];
  291. // Loop through stringtable and add flipped items to $returnValue
  292. foreach ($stringTable as $key => $value) {
  293. if (!$value instanceof RichText) {
  294. $returnValue[$value] = $key;
  295. } elseif ($value instanceof RichText) {
  296. $returnValue[$value->getHashCode()] = $key;
  297. }
  298. }
  299. return $returnValue;
  300. }
  301. }