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