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