1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spreadsheet\Writer; |
4
|
|
|
|
5
|
|
|
use Spreadsheet\Calculation; |
6
|
|
|
use Spreadsheet\Shared\Font; |
7
|
|
|
use Spreadsheet\Shared\StringHelper; |
8
|
|
|
use Spreadsheet\Spreadsheet; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Spreadsheet_Writer_HTML |
12
|
|
|
* |
13
|
|
|
* Copyright (c) 2006 - 2015 Spreadsheet |
14
|
|
|
* |
15
|
|
|
* This library is free software; you can redistribute it and/or |
16
|
|
|
* modify it under the terms of the GNU Lesser General Public |
17
|
|
|
* License as published by the Free Software Foundation; either |
18
|
|
|
* version 2.1 of the License, or (at your option) any later version. |
19
|
|
|
* |
20
|
|
|
* This library is distributed in the hope that it will be useful, |
21
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
22
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
23
|
|
|
* Lesser General Public License for more details. |
24
|
|
|
* |
25
|
|
|
* You should have received a copy of the GNU Lesser General Public |
26
|
|
|
* License along with this library; if not, write to the Free Software |
27
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
28
|
|
|
* |
29
|
|
|
* @category Spreadsheet |
30
|
|
|
* @copyright Copyright (c) 2006 - 2015 Spreadsheet (https://github.com/PHPOffice/Spreadsheet) |
31
|
|
|
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL |
32
|
|
|
* @version ##VERSION##, ##DATE## |
33
|
|
|
*/ |
34
|
|
|
class HTML extends BaseWriter implements IWriter |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* Spreadsheet object |
38
|
|
|
* |
39
|
|
|
* @var Spreadsheet |
40
|
|
|
*/ |
41
|
|
|
protected $spreadsheet; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Sheet index to write |
45
|
|
|
* |
46
|
|
|
* @var int |
47
|
|
|
*/ |
48
|
|
|
private $sheetIndex = 0; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Images root |
52
|
|
|
* |
53
|
|
|
* @var string |
54
|
|
|
*/ |
55
|
|
|
private $imagesRoot = '.'; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* embed images, or link to images |
59
|
|
|
* |
60
|
|
|
* @var boolean |
61
|
|
|
*/ |
62
|
|
|
private $embedImages = false; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Use inline CSS? |
66
|
|
|
* |
67
|
|
|
* @var boolean |
68
|
|
|
*/ |
69
|
|
|
private $useInlineCss = false; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Array of CSS styles |
73
|
|
|
* |
74
|
|
|
* @var array |
75
|
|
|
*/ |
76
|
|
|
private $cssStyles; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Array of column widths in points |
80
|
|
|
* |
81
|
|
|
* @var array |
82
|
|
|
*/ |
83
|
|
|
private $columnWidths; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Default font |
87
|
|
|
* |
88
|
|
|
* @var \Spreadsheet\Style\Font |
89
|
|
|
*/ |
90
|
|
|
private $defaultFont; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Flag whether spans have been calculated |
94
|
|
|
* |
95
|
|
|
* @var boolean |
96
|
|
|
*/ |
97
|
|
|
private $spansAreCalculated = false; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Excel cells that should not be written as HTML cells |
101
|
|
|
* |
102
|
|
|
* @var array |
103
|
|
|
*/ |
104
|
|
|
private $isSpannedCell = array(); |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Excel cells that are upper-left corner in a cell merge |
108
|
|
|
* |
109
|
|
|
* @var array |
110
|
|
|
*/ |
111
|
|
|
private $isBaseCell = array(); |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Excel rows that should not be written as HTML rows |
115
|
|
|
* |
116
|
|
|
* @var array |
117
|
|
|
*/ |
118
|
|
|
private $isSpannedRow = array(); |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Is the current writer creating PDF? |
122
|
|
|
* |
123
|
|
|
* @var boolean |
124
|
|
|
*/ |
125
|
|
|
protected $isPdf = false; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Generate the Navigation block |
129
|
|
|
* |
130
|
|
|
* @var boolean |
131
|
|
|
*/ |
132
|
|
|
private $generateSheetNavigationBlock = true; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Create a new Spreadsheet_Writer_HTML |
136
|
|
|
* |
137
|
|
|
* @param Spreadsheet $spreadsheet |
138
|
|
|
*/ |
139
|
|
|
public function __construct(Spreadsheet $spreadsheet) |
140
|
|
|
{ |
141
|
|
|
$this->spreadsheet = $spreadsheet; |
142
|
|
|
$this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Save Spreadsheet to file |
147
|
|
|
* |
148
|
|
|
* @param string $pFilename |
149
|
|
|
* @throws \Spreadsheet\Writer\Exception |
150
|
|
|
*/ |
151
|
|
|
public function save($pFilename = null) |
152
|
|
|
{ |
153
|
|
|
// garbage collect |
154
|
|
|
$this->spreadsheet->garbageCollect(); |
155
|
|
|
|
156
|
|
|
$saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); |
157
|
|
|
Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); |
158
|
|
|
$saveArrayReturnType = Calculation::getArrayReturnType(); |
159
|
|
|
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); |
160
|
|
|
|
161
|
|
|
// Build CSS |
162
|
|
|
$this->buildCSS(!$this->useInlineCss); |
163
|
|
|
|
164
|
|
|
// Open file |
165
|
|
|
$fileHandle = fopen($pFilename, 'wb+'); |
166
|
|
|
if ($fileHandle === false) { |
167
|
|
|
throw new \Spreadsheet\Writer\Exception("Could not open file $pFilename for writing."); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// Write headers |
171
|
|
|
fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); |
172
|
|
|
|
173
|
|
|
// Write navigation (tabs) |
174
|
|
|
if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { |
175
|
|
|
fwrite($fileHandle, $this->generateNavigation()); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
// Write data |
179
|
|
|
fwrite($fileHandle, $this->generateSheetData()); |
180
|
|
|
|
181
|
|
|
// Write footer |
182
|
|
|
fwrite($fileHandle, $this->generateHTMLFooter()); |
183
|
|
|
|
184
|
|
|
// Close file |
185
|
|
|
fclose($fileHandle); |
186
|
|
|
|
187
|
|
|
Calculation::setArrayReturnType($saveArrayReturnType); |
188
|
|
|
Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Map VAlign |
193
|
|
|
* |
194
|
|
|
* @param string $vAlign Vertical alignment |
195
|
|
|
* @return string |
196
|
|
|
*/ |
197
|
|
|
private function mapVAlign($vAlign) |
198
|
|
|
{ |
199
|
|
|
switch ($vAlign) { |
200
|
|
|
case \Spreadsheet\Style\Alignment::VERTICAL_BOTTOM: |
201
|
|
|
return 'bottom'; |
202
|
|
|
case \Spreadsheet\Style\Alignment::VERTICAL_TOP: |
203
|
|
|
return 'top'; |
204
|
|
|
case \Spreadsheet\Style\Alignment::VERTICAL_CENTER: |
205
|
|
|
case \Spreadsheet\Style\Alignment::VERTICAL_JUSTIFY: |
206
|
|
|
return 'middle'; |
207
|
|
|
default: |
208
|
|
|
return 'baseline'; |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Map HAlign |
214
|
|
|
* |
215
|
|
|
* @param string $hAlign Horizontal alignment |
216
|
|
|
* @return string|false |
217
|
|
|
*/ |
218
|
|
|
private function mapHAlign($hAlign) |
219
|
|
|
{ |
220
|
|
|
switch ($hAlign) { |
221
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_GENERAL: |
222
|
|
|
return false; |
223
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_LEFT: |
224
|
|
|
return 'left'; |
225
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_RIGHT: |
226
|
|
|
return 'right'; |
227
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_CENTER: |
228
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_CENTER_CONTINUOUS: |
229
|
|
|
return 'center'; |
230
|
|
|
case \Spreadsheet\Style\Alignment::HORIZONTAL_JUSTIFY: |
231
|
|
|
return 'justify'; |
232
|
|
|
default: |
233
|
|
|
return false; |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Map border style |
239
|
|
|
* |
240
|
|
|
* @param int $borderStyle Sheet index |
241
|
|
|
* @return string |
242
|
|
|
*/ |
243
|
|
|
private function mapBorderStyle($borderStyle) |
244
|
|
|
{ |
245
|
|
|
switch ($borderStyle) { |
246
|
|
|
case \Spreadsheet\Style\Border::BORDER_NONE: |
247
|
|
|
return 'none'; |
248
|
|
|
case \Spreadsheet\Style\Border::BORDER_DASHDOT: |
249
|
|
|
return '1px dashed'; |
250
|
|
|
case \Spreadsheet\Style\Border::BORDER_DASHDOTDOT: |
251
|
|
|
return '1px dotted'; |
252
|
|
|
case \Spreadsheet\Style\Border::BORDER_DASHED: |
253
|
|
|
return '1px dashed'; |
254
|
|
|
case \Spreadsheet\Style\Border::BORDER_DOTTED: |
255
|
|
|
return '1px dotted'; |
256
|
|
|
case \Spreadsheet\Style\Border::BORDER_DOUBLE: |
257
|
|
|
return '3px double'; |
258
|
|
|
case \Spreadsheet\Style\Border::BORDER_HAIR: |
259
|
|
|
return '1px solid'; |
260
|
|
|
case \Spreadsheet\Style\Border::BORDER_MEDIUM: |
261
|
|
|
return '2px solid'; |
262
|
|
|
case \Spreadsheet\Style\Border::BORDER_MEDIUMDASHDOT: |
263
|
|
|
return '2px dashed'; |
264
|
|
|
case \Spreadsheet\Style\Border::BORDER_MEDIUMDASHDOTDOT: |
265
|
|
|
return '2px dotted'; |
266
|
|
|
case \Spreadsheet\Style\Border::BORDER_MEDIUMDASHED: |
267
|
|
|
return '2px dashed'; |
268
|
|
|
case \Spreadsheet\Style\Border::BORDER_SLANTDASHDOT: |
269
|
|
|
return '2px dashed'; |
270
|
|
|
case \Spreadsheet\Style\Border::BORDER_THICK: |
271
|
|
|
return '3px solid'; |
272
|
|
|
case \Spreadsheet\Style\Border::BORDER_THIN: |
273
|
|
|
return '1px solid'; |
274
|
|
|
default: |
275
|
|
|
// map others to thin |
276
|
|
|
return '1px solid'; |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Get sheet index |
282
|
|
|
* |
283
|
|
|
* @return int |
284
|
|
|
*/ |
285
|
|
|
public function getSheetIndex() |
286
|
|
|
{ |
287
|
|
|
return $this->sheetIndex; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Set sheet index |
292
|
|
|
* |
293
|
|
|
* @param int $pValue Sheet index |
294
|
|
|
* @return HTML |
295
|
|
|
*/ |
296
|
|
|
public function setSheetIndex($pValue = 0) |
297
|
|
|
{ |
298
|
|
|
$this->sheetIndex = $pValue; |
299
|
|
|
return $this; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Get sheet index |
304
|
|
|
* |
305
|
|
|
* @return boolean |
306
|
|
|
*/ |
307
|
|
|
public function getGenerateSheetNavigationBlock() |
308
|
|
|
{ |
309
|
|
|
return $this->generateSheetNavigationBlock; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Set sheet index |
314
|
|
|
* |
315
|
|
|
* @param boolean $pValue Flag indicating whether the sheet navigation block should be generated or not |
316
|
|
|
* @return HTML |
317
|
|
|
*/ |
318
|
|
|
public function setGenerateSheetNavigationBlock($pValue = true) |
319
|
|
|
{ |
320
|
|
|
$this->generateSheetNavigationBlock = (bool) $pValue; |
321
|
|
|
return $this; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Write all sheets (resets sheetIndex to NULL) |
326
|
|
|
*/ |
327
|
|
|
public function writeAllSheets() |
328
|
|
|
{ |
329
|
|
|
$this->sheetIndex = null; |
330
|
|
|
return $this; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Generate HTML header |
335
|
|
|
* |
336
|
|
|
* @param boolean $pIncludeStyles Include styles? |
337
|
|
|
* @return string |
338
|
|
|
* @throws \Spreadsheet\Writer\Exception |
339
|
|
|
*/ |
340
|
|
|
public function generateHTMLHeader($pIncludeStyles = false) |
341
|
|
|
{ |
342
|
|
|
// Spreadsheet object known? |
343
|
|
|
if (is_null($this->spreadsheet)) { |
344
|
|
|
throw new \Spreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.'); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
// Construct HTML |
348
|
|
|
$properties = $this->spreadsheet->getProperties(); |
349
|
|
|
$html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' . PHP_EOL; |
350
|
|
|
$html .= '<!-- Generated by Spreadsheet - https://github.com/PHPOffice/Spreadsheet -->' . PHP_EOL; |
351
|
|
|
$html .= '<html>' . PHP_EOL; |
352
|
|
|
$html .= ' <head>' . PHP_EOL; |
353
|
|
|
$html .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . PHP_EOL; |
354
|
|
|
if ($properties->getTitle() > '') { |
355
|
|
|
$html .= ' <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL; |
356
|
|
|
} |
357
|
|
|
if ($properties->getCreator() > '') { |
358
|
|
|
$html .= ' <meta name="author" content="' . htmlspecialchars($properties->getCreator()) . '" />' . PHP_EOL; |
359
|
|
|
} |
360
|
|
|
if ($properties->getTitle() > '') { |
361
|
|
|
$html .= ' <meta name="title" content="' . htmlspecialchars($properties->getTitle()) . '" />' . PHP_EOL; |
362
|
|
|
} |
363
|
|
View Code Duplication |
if ($properties->getDescription() > '') { |
|
|
|
|
364
|
|
|
$html .= ' <meta name="description" content="' . htmlspecialchars($properties->getDescription()) . '" />' . PHP_EOL; |
365
|
|
|
} |
366
|
|
|
if ($properties->getSubject() > '') { |
367
|
|
|
$html .= ' <meta name="subject" content="' . htmlspecialchars($properties->getSubject()) . '" />' . PHP_EOL; |
368
|
|
|
} |
369
|
|
|
if ($properties->getKeywords() > '') { |
370
|
|
|
$html .= ' <meta name="keywords" content="' . htmlspecialchars($properties->getKeywords()) . '" />' . PHP_EOL; |
371
|
|
|
} |
372
|
|
|
if ($properties->getCategory() > '') { |
373
|
|
|
$html .= ' <meta name="category" content="' . htmlspecialchars($properties->getCategory()) . '" />' . PHP_EOL; |
374
|
|
|
} |
375
|
|
View Code Duplication |
if ($properties->getCompany() > '') { |
|
|
|
|
376
|
|
|
$html .= ' <meta name="company" content="' . htmlspecialchars($properties->getCompany()) . '" />' . PHP_EOL; |
377
|
|
|
} |
378
|
|
View Code Duplication |
if ($properties->getManager() > '') { |
|
|
|
|
379
|
|
|
$html .= ' <meta name="manager" content="' . htmlspecialchars($properties->getManager()) . '" />' . PHP_EOL; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
if ($pIncludeStyles) { |
383
|
|
|
$html .= $this->generateStyles(true); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$html .= ' </head>' . PHP_EOL; |
387
|
|
|
$html .= '' . PHP_EOL; |
388
|
|
|
$html .= ' <body>' . PHP_EOL; |
389
|
|
|
|
390
|
|
|
return $html; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Generate sheet data |
395
|
|
|
* |
396
|
|
|
* @return string |
397
|
|
|
* @throws \Spreadsheet\Writer\Exception |
398
|
|
|
*/ |
399
|
|
|
public function generateSheetData() |
400
|
|
|
{ |
401
|
|
|
// Spreadsheet object known? |
402
|
|
|
if (is_null($this->spreadsheet)) { |
403
|
|
|
throw new \Spreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.'); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
// Ensure that Spans have been calculated? |
407
|
|
|
if ($this->sheetIndex !== null || !$this->spansAreCalculated) { |
408
|
|
|
$this->calculateSpans(); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
// Fetch sheets |
412
|
|
|
$sheets = array(); |
413
|
|
View Code Duplication |
if (is_null($this->sheetIndex)) { |
|
|
|
|
414
|
|
|
$sheets = $this->spreadsheet->getAllSheets(); |
415
|
|
|
} else { |
416
|
|
|
$sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
// Construct HTML |
420
|
|
|
$html = ''; |
421
|
|
|
|
422
|
|
|
// Loop all sheets |
423
|
|
|
$sheetId = 0; |
424
|
|
|
foreach ($sheets as $sheet) { |
425
|
|
|
// Write table header |
426
|
|
|
$html .= $this->generateTableHeader($sheet); |
427
|
|
|
|
428
|
|
|
// Get worksheet dimension |
429
|
|
|
$dimension = explode(':', $sheet->calculateWorksheetDimension()); |
430
|
|
|
$dimension[0] = \Spreadsheet\Cell::coordinateFromString($dimension[0]); |
431
|
|
|
$dimension[0][0] = \Spreadsheet\Cell::columnIndexFromString($dimension[0][0]) - 1; |
432
|
|
|
$dimension[1] = \Spreadsheet\Cell::coordinateFromString($dimension[1]); |
433
|
|
|
$dimension[1][0] = \Spreadsheet\Cell::columnIndexFromString($dimension[1][0]) - 1; |
434
|
|
|
|
435
|
|
|
// row min,max |
436
|
|
|
$rowMin = $dimension[0][1]; |
437
|
|
|
$rowMax = $dimension[1][1]; |
438
|
|
|
|
439
|
|
|
// calculate start of <tbody>, <thead> |
440
|
|
|
$tbodyStart = $rowMin; |
441
|
|
|
$theadStart = $theadEnd = 0; // default: no <thead> no </thead> |
442
|
|
|
if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { |
443
|
|
|
$rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); |
444
|
|
|
|
445
|
|
|
// we can only support repeating rows that start at top row |
446
|
|
|
if ($rowsToRepeatAtTop[0] == 1) { |
447
|
|
|
$theadStart = $rowsToRepeatAtTop[0]; |
448
|
|
|
$theadEnd = $rowsToRepeatAtTop[1]; |
449
|
|
|
$tbodyStart = $rowsToRepeatAtTop[1] + 1; |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
// Loop through cells |
454
|
|
|
$row = $rowMin-1; |
455
|
|
|
while ($row++ < $rowMax) { |
456
|
|
|
// <thead> ? |
457
|
|
|
if ($row == $theadStart) { |
458
|
|
|
$html .= ' <thead>' . PHP_EOL; |
459
|
|
|
$cellType = 'th'; |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
// <tbody> ? |
463
|
|
|
if ($row == $tbodyStart) { |
464
|
|
|
$html .= ' <tbody>' . PHP_EOL; |
465
|
|
|
$cellType = 'td'; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
// Write row if there are HTML table cells in it |
469
|
|
|
if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { |
470
|
|
|
// Start a new rowData |
471
|
|
|
$rowData = array(); |
472
|
|
|
// Loop through columns |
473
|
|
|
$column = $dimension[0][0] - 1; |
474
|
|
|
while ($column++ < $dimension[1][0]) { |
475
|
|
|
// Cell exists? |
476
|
|
|
if ($sheet->cellExistsByColumnAndRow($column, $row)) { |
477
|
|
|
$rowData[$column] = \Spreadsheet\Cell::stringFromColumnIndex($column) . $row; |
478
|
|
|
} else { |
479
|
|
|
$rowData[$column] = ''; |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
$html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); |
|
|
|
|
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
// </thead> ? |
486
|
|
|
if ($row == $theadEnd) { |
487
|
|
|
$html .= ' </thead>' . PHP_EOL; |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
$html .= $this->extendRowsForChartsAndImages($sheet, $row); |
491
|
|
|
|
492
|
|
|
// Close table body. |
493
|
|
|
$html .= ' </tbody>' . PHP_EOL; |
494
|
|
|
|
495
|
|
|
// Write table footer |
496
|
|
|
$html .= $this->generateTableFooter(); |
497
|
|
|
|
498
|
|
|
// Writing PDF? |
499
|
|
|
if ($this->isPdf) { |
500
|
|
|
if (is_null($this->sheetIndex) && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { |
501
|
|
|
$html .= '<div style="page-break-before:always" />'; |
502
|
|
|
} |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
// Next sheet |
506
|
|
|
++$sheetId; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
return $html; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
/** |
513
|
|
|
* Generate sheet tabs |
514
|
|
|
* |
515
|
|
|
* @return string |
516
|
|
|
* @throws \Spreadsheet\Writer\Exception |
517
|
|
|
*/ |
518
|
|
|
public function generateNavigation() |
519
|
|
|
{ |
520
|
|
|
// Spreadsheet object known? |
521
|
|
|
if (is_null($this->spreadsheet)) { |
522
|
|
|
throw new \Spreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.'); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
// Fetch sheets |
526
|
|
|
$sheets = array(); |
527
|
|
View Code Duplication |
if (is_null($this->sheetIndex)) { |
|
|
|
|
528
|
|
|
$sheets = $this->spreadsheet->getAllSheets(); |
529
|
|
|
} else { |
530
|
|
|
$sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
// Construct HTML |
534
|
|
|
$html = ''; |
535
|
|
|
|
536
|
|
|
// Only if there are more than 1 sheets |
537
|
|
|
if (count($sheets) > 1) { |
538
|
|
|
// Loop all sheets |
539
|
|
|
$sheetId = 0; |
540
|
|
|
|
541
|
|
|
$html .= '<ul class="navigation">' . PHP_EOL; |
542
|
|
|
|
543
|
|
|
foreach ($sheets as $sheet) { |
544
|
|
|
$html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL; |
545
|
|
|
++$sheetId; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
$html .= '</ul>' . PHP_EOL; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
return $html; |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
private function extendRowsForChartsAndImages(\Spreadsheet\Worksheet $pSheet, $row) |
555
|
|
|
{ |
556
|
|
|
$rowMax = $row; |
557
|
|
|
$colMax = 'A'; |
558
|
|
|
if ($this->includeCharts) { |
559
|
|
|
foreach ($pSheet->getChartCollection() as $chart) { |
560
|
|
|
if ($chart instanceof Spreadsheet_Chart) { |
|
|
|
|
561
|
|
|
$chartCoordinates = $chart->getTopLeftPosition(); |
562
|
|
|
$chartTL = \Spreadsheet\Cell::coordinateFromString($chartCoordinates['cell']); |
563
|
|
|
$chartCol = \Spreadsheet\Cell::columnIndexFromString($chartTL[0]); |
564
|
|
|
if ($chartTL[1] > $rowMax) { |
565
|
|
|
$rowMax = $chartTL[1]; |
566
|
|
|
if ($chartCol > \Spreadsheet\Cell::columnIndexFromString($colMax)) { |
567
|
|
|
$colMax = $chartTL[0]; |
568
|
|
|
} |
569
|
|
|
} |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
foreach ($pSheet->getDrawingCollection() as $drawing) { |
575
|
|
|
if ($drawing instanceof \Spreadsheet\Worksheet\Drawing) { |
|
|
|
|
576
|
|
|
$imageTL = \Spreadsheet\Cell::coordinateFromString($drawing->getCoordinates()); |
577
|
|
|
$imageCol = \Spreadsheet\Cell::columnIndexFromString($imageTL[0]); |
578
|
|
|
if ($imageTL[1] > $rowMax) { |
579
|
|
|
$rowMax = $imageTL[1]; |
580
|
|
|
if ($imageCol > \Spreadsheet\Cell::columnIndexFromString($colMax)) { |
581
|
|
|
$colMax = $imageTL[0]; |
582
|
|
|
} |
583
|
|
|
} |
584
|
|
|
} |
585
|
|
|
} |
586
|
|
|
$html = ''; |
587
|
|
|
$colMax++; |
588
|
|
|
while ($row <= $rowMax) { |
589
|
|
|
$html .= '<tr>'; |
590
|
|
|
for ($col = 'A'; $col != $colMax; ++$col) { |
591
|
|
|
$html .= '<td>'; |
592
|
|
|
$html .= $this->writeImageInCell($pSheet, $col.$row); |
593
|
|
|
if ($this->includeCharts) { |
594
|
|
|
$html .= $this->writeChartInCell($pSheet, $col.$row); |
595
|
|
|
} |
596
|
|
|
$html .= '</td>'; |
597
|
|
|
} |
598
|
|
|
++$row; |
599
|
|
|
$html .= '</tr>'; |
600
|
|
|
} |
601
|
|
|
return $html; |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Generate image tag in cell |
607
|
|
|
* |
608
|
|
|
* @param \Spreadsheet\Worksheet $pSheet \Spreadsheet\Worksheet |
609
|
|
|
* @param string $coordinates Cell coordinates |
610
|
|
|
* @return string |
611
|
|
|
* @throws \Spreadsheet\Writer\Exception |
612
|
|
|
*/ |
613
|
|
|
private function writeImageInCell(\Spreadsheet\Worksheet $pSheet, $coordinates) |
614
|
|
|
{ |
615
|
|
|
// Construct HTML |
616
|
|
|
$html = ''; |
617
|
|
|
|
618
|
|
|
// Write images |
619
|
|
|
foreach ($pSheet->getDrawingCollection() as $drawing) { |
620
|
|
|
if ($drawing instanceof \Spreadsheet\Worksheet\Drawing) { |
|
|
|
|
621
|
|
|
if ($drawing->getCoordinates() == $coordinates) { |
622
|
|
|
$filename = $drawing->getPath(); |
623
|
|
|
|
624
|
|
|
// Strip off eventual '.' |
625
|
|
|
if (substr($filename, 0, 1) == '.') { |
626
|
|
|
$filename = substr($filename, 1); |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
// Prepend images root |
630
|
|
|
$filename = $this->getImagesRoot() . $filename; |
631
|
|
|
|
632
|
|
|
// Strip off eventual '.' |
633
|
|
|
if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') { |
634
|
|
|
$filename = substr($filename, 1); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
// Convert UTF8 data to PCDATA |
638
|
|
|
$filename = htmlspecialchars($filename); |
639
|
|
|
|
640
|
|
|
$html .= PHP_EOL; |
641
|
|
|
if ((!$this->embedImages) || ($this->isPdf)) { |
642
|
|
|
$imageData = $filename; |
643
|
|
|
} else { |
644
|
|
|
$imageDetails = getimagesize($filename); |
645
|
|
|
if ($fp = fopen($filename, "rb", 0)) { |
646
|
|
|
$picture = fread($fp, filesize($filename)); |
647
|
|
|
fclose($fp); |
648
|
|
|
// base64 encode the binary data, then break it |
649
|
|
|
// into chunks according to RFC 2045 semantics |
650
|
|
|
$base64 = chunk_split(base64_encode($picture)); |
651
|
|
|
$imageData = 'data:'.$imageDetails['mime'].';base64,' . $base64; |
652
|
|
|
} else { |
653
|
|
|
$imageData = $filename; |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
$html .= '<div style="position: relative;">'; |
658
|
|
|
$html .= '<img style="position: absolute; z-index: 1; left: ' . |
659
|
|
|
$drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' . |
660
|
|
|
$drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' . |
661
|
|
|
$imageData . '" border="0" />'; |
662
|
|
|
$html .= '</div>'; |
663
|
|
|
} |
664
|
|
|
} elseif ($drawing instanceof \Spreadsheet\Worksheet\MemoryDrawing) { |
|
|
|
|
665
|
|
|
if ($drawing->getCoordinates() != $coordinates) { |
666
|
|
|
continue; |
667
|
|
|
} |
668
|
|
|
ob_start(); // Let's start output buffering. |
669
|
|
|
imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't. |
670
|
|
|
$contents = ob_get_contents(); // Instead, output above is saved to $contents |
671
|
|
|
ob_end_clean(); // End the output buffer. |
672
|
|
|
|
673
|
|
|
$dataUri = "data:image/jpeg;base64," . base64_encode($contents); |
674
|
|
|
|
675
|
|
|
// Because of the nature of tables, width is more important than height. |
676
|
|
|
// max-width: 100% ensures that image doesnt overflow containing cell |
677
|
|
|
// width: X sets width of supplied image. |
678
|
|
|
// As a result, images bigger than cell will be contained and images smaller will not get stretched |
679
|
|
|
$html .= '<img src="'.$dataUri.'" style="max-width:100%;width:'.$drawing->getWidth().'px;" />'; |
680
|
|
|
} |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
return $html; |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
/** |
687
|
|
|
* Generate chart tag in cell |
688
|
|
|
* |
689
|
|
|
* @param \Spreadsheet\Worksheet $pSheet \Spreadsheet\Worksheet |
690
|
|
|
* @param string $coordinates Cell coordinates |
691
|
|
|
* @return string |
692
|
|
|
* @throws \Spreadsheet\Writer\Exception |
693
|
|
|
*/ |
694
|
|
|
private function writeChartInCell(\Spreadsheet\Worksheet $pSheet, $coordinates) |
695
|
|
|
{ |
696
|
|
|
// Construct HTML |
697
|
|
|
$html = ''; |
698
|
|
|
|
699
|
|
|
// Write charts |
700
|
|
|
foreach ($pSheet->getChartCollection() as $chart) { |
701
|
|
|
if ($chart instanceof Spreadsheet_Chart) { |
|
|
|
|
702
|
|
|
$chartCoordinates = $chart->getTopLeftPosition(); |
703
|
|
|
if ($chartCoordinates['cell'] == $coordinates) { |
704
|
|
|
$chartFileName = \Spreadsheet\Shared\File::sysGetTempDir().'/'.uniqid().'.png'; |
705
|
|
|
if (!$chart->render($chartFileName)) { |
706
|
|
|
return; |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
$html .= PHP_EOL; |
710
|
|
|
$imageDetails = getimagesize($chartFileName); |
711
|
|
|
if ($fp = fopen($chartFileName, "rb", 0)) { |
712
|
|
|
$picture = fread($fp, filesize($chartFileName)); |
713
|
|
|
fclose($fp); |
714
|
|
|
// base64 encode the binary data, then break it |
715
|
|
|
// into chunks according to RFC 2045 semantics |
716
|
|
|
$base64 = chunk_split(base64_encode($picture)); |
717
|
|
|
$imageData = 'data:'.$imageDetails['mime'].';base64,' . $base64; |
718
|
|
|
|
719
|
|
|
$html .= '<div style="position: relative;">'; |
720
|
|
|
$html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" border="0" />' . PHP_EOL; |
721
|
|
|
$html .= '</div>'; |
722
|
|
|
|
723
|
|
|
unlink($chartFileName); |
724
|
|
|
} |
725
|
|
|
} |
726
|
|
|
} |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
// Return |
730
|
|
|
return $html; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
|
|
* Generate CSS styles |
735
|
|
|
* |
736
|
|
|
* @param boolean $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) |
737
|
|
|
* @return string |
738
|
|
|
* @throws \Spreadsheet\Writer\Exception |
739
|
|
|
*/ |
740
|
|
|
public function generateStyles($generateSurroundingHTML = true) |
741
|
|
|
{ |
742
|
|
|
// Spreadsheet object known? |
743
|
|
|
if (is_null($this->spreadsheet)) { |
744
|
|
|
throw new \Spreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.'); |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
// Build CSS |
748
|
|
|
$css = $this->buildCSS($generateSurroundingHTML); |
749
|
|
|
|
750
|
|
|
// Construct HTML |
751
|
|
|
$html = ''; |
752
|
|
|
|
753
|
|
|
// Start styles |
754
|
|
|
if ($generateSurroundingHTML) { |
755
|
|
|
$html .= ' <style type="text/css">' . PHP_EOL; |
756
|
|
|
$html .= ' html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL; |
757
|
|
|
} |
758
|
|
|
|
759
|
|
|
// Write all other styles |
760
|
|
|
foreach ($css as $styleName => $styleDefinition) { |
761
|
|
|
if ($styleName != 'html') { |
762
|
|
|
$html .= ' ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL; |
763
|
|
|
} |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
// End styles |
767
|
|
|
if ($generateSurroundingHTML) { |
768
|
|
|
$html .= ' </style>' . PHP_EOL; |
769
|
|
|
} |
770
|
|
|
|
771
|
|
|
// Return |
772
|
|
|
return $html; |
773
|
|
|
} |
774
|
|
|
|
775
|
|
|
/** |
776
|
|
|
* Build CSS styles |
777
|
|
|
* |
778
|
|
|
* @param boolean $generateSurroundingHTML Generate surrounding HTML style? (html { }) |
779
|
|
|
* @return array |
780
|
|
|
* @throws \Spreadsheet\Writer\Exception |
781
|
|
|
*/ |
782
|
|
|
public function buildCSS($generateSurroundingHTML = true) |
783
|
|
|
{ |
784
|
|
|
// Spreadsheet object known? |
785
|
|
|
if (is_null($this->spreadsheet)) { |
786
|
|
|
throw new \Spreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.'); |
787
|
|
|
} |
788
|
|
|
|
789
|
|
|
// Cached? |
790
|
|
|
if (!is_null($this->cssStyles)) { |
791
|
|
|
return $this->cssStyles; |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
// Ensure that spans have been calculated |
795
|
|
|
if (!$this->spansAreCalculated) { |
796
|
|
|
$this->calculateSpans(); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
// Construct CSS |
800
|
|
|
$css = array(); |
801
|
|
|
|
802
|
|
|
// Start styles |
803
|
|
|
if ($generateSurroundingHTML) { |
804
|
|
|
// html { } |
805
|
|
|
$css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif'; |
806
|
|
|
$css['html']['font-size'] = '11pt'; |
807
|
|
|
$css['html']['background-color'] = 'white'; |
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
|
811
|
|
|
// table { } |
812
|
|
|
$css['table']['border-collapse'] = 'collapse'; |
813
|
|
|
if (!$this->isPdf) { |
814
|
|
|
$css['table']['page-break-after'] = 'always'; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
// .gridlines td { } |
818
|
|
|
$css['.gridlines td']['border'] = '1px dotted black'; |
819
|
|
|
$css['.gridlines th']['border'] = '1px dotted black'; |
820
|
|
|
|
821
|
|
|
// .b {} |
822
|
|
|
$css['.b']['text-align'] = 'center'; // BOOL |
823
|
|
|
|
824
|
|
|
// .e {} |
825
|
|
|
$css['.e']['text-align'] = 'center'; // ERROR |
826
|
|
|
|
827
|
|
|
// .f {} |
828
|
|
|
$css['.f']['text-align'] = 'right'; // FORMULA |
829
|
|
|
|
830
|
|
|
// .inlineStr {} |
831
|
|
|
$css['.inlineStr']['text-align'] = 'left'; // INLINE |
832
|
|
|
|
833
|
|
|
// .n {} |
834
|
|
|
$css['.n']['text-align'] = 'right'; // NUMERIC |
835
|
|
|
|
836
|
|
|
// .s {} |
837
|
|
|
$css['.s']['text-align'] = 'left'; // STRING |
838
|
|
|
|
839
|
|
|
// Calculate cell style hashes |
840
|
|
|
foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) { |
841
|
|
|
$css['td.style' . $index] = $this->createCSSStyle($style); |
842
|
|
|
$css['th.style' . $index] = $this->createCSSStyle($style); |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
// Fetch sheets |
846
|
|
|
$sheets = array(); |
847
|
|
View Code Duplication |
if (is_null($this->sheetIndex)) { |
|
|
|
|
848
|
|
|
$sheets = $this->spreadsheet->getAllSheets(); |
849
|
|
|
} else { |
850
|
|
|
$sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
// Build styles per sheet |
854
|
|
|
foreach ($sheets as $sheet) { |
855
|
|
|
// Calculate hash code |
856
|
|
|
$sheetIndex = $sheet->getParent()->getIndex($sheet); |
857
|
|
|
|
858
|
|
|
// Build styles |
859
|
|
|
// Calculate column widths |
860
|
|
|
$sheet->calculateColumnWidths(); |
861
|
|
|
|
862
|
|
|
// col elements, initialize |
863
|
|
|
$highestColumnIndex = \Spreadsheet\Cell::columnIndexFromString($sheet->getHighestColumn()) - 1; |
864
|
|
|
$column = -1; |
865
|
|
|
while ($column++ < $highestColumnIndex) { |
866
|
|
|
$this->columnWidths[$sheetIndex][$column] = 42; // approximation |
867
|
|
|
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt'; |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
// col elements, loop through columnDimensions and set width |
871
|
|
|
foreach ($sheet->getColumnDimensions() as $columnDimension) { |
872
|
|
|
if (($width = \Spreadsheet\Shared\Drawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont)) >= 0) { |
873
|
|
|
$width = \Spreadsheet\Shared\Drawing::pixelsToPoints($width); |
874
|
|
|
$column = \Spreadsheet\Cell::columnIndexFromString($columnDimension->getColumnIndex()) - 1; |
875
|
|
|
$this->columnWidths[$sheetIndex][$column] = $width; |
876
|
|
|
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt'; |
877
|
|
|
|
878
|
|
View Code Duplication |
if ($columnDimension->getVisible() === false) { |
|
|
|
|
879
|
|
|
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['visibility'] = 'collapse'; |
880
|
|
|
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['*display'] = 'none'; // target IE6+7 |
881
|
|
|
} |
882
|
|
|
} |
883
|
|
|
} |
884
|
|
|
|
885
|
|
|
// Default row height |
886
|
|
|
$rowDimension = $sheet->getDefaultRowDimension(); |
887
|
|
|
|
888
|
|
|
// table.sheetN tr { } |
889
|
|
|
$css['table.sheet' . $sheetIndex . ' tr'] = array(); |
890
|
|
|
|
891
|
|
View Code Duplication |
if ($rowDimension->getRowHeight() == -1) { |
|
|
|
|
892
|
|
|
$pt_height = Font::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont()); |
893
|
|
|
} else { |
894
|
|
|
$pt_height = $rowDimension->getRowHeight(); |
895
|
|
|
} |
896
|
|
|
$css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt'; |
897
|
|
|
if ($rowDimension->getVisible() === false) { |
898
|
|
|
$css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none'; |
899
|
|
|
$css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden'; |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
// Calculate row heights |
903
|
|
|
foreach ($sheet->getRowDimensions() as $rowDimension) { |
904
|
|
|
$row = $rowDimension->getRowIndex() - 1; |
905
|
|
|
|
906
|
|
|
// table.sheetN tr.rowYYYYYY { } |
907
|
|
|
$css['table.sheet' . $sheetIndex . ' tr.row' . $row] = array(); |
908
|
|
|
|
909
|
|
View Code Duplication |
if ($rowDimension->getRowHeight() == -1) { |
|
|
|
|
910
|
|
|
$pt_height = Font::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont()); |
911
|
|
|
} else { |
912
|
|
|
$pt_height = $rowDimension->getRowHeight(); |
913
|
|
|
} |
914
|
|
|
$css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt'; |
915
|
|
View Code Duplication |
if ($rowDimension->getVisible() === false) { |
|
|
|
|
916
|
|
|
$css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none'; |
917
|
|
|
$css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden'; |
918
|
|
|
} |
919
|
|
|
} |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
// Cache |
923
|
|
|
if (is_null($this->cssStyles)) { |
924
|
|
|
$this->cssStyles = $css; |
925
|
|
|
} |
926
|
|
|
|
927
|
|
|
// Return |
928
|
|
|
return $css; |
929
|
|
|
} |
930
|
|
|
|
931
|
|
|
/** |
932
|
|
|
* Create CSS style |
933
|
|
|
* |
934
|
|
|
* @param \Spreadsheet\Style $pStyle Spreadsheet_Style |
935
|
|
|
* @return array |
936
|
|
|
*/ |
937
|
|
|
private function createCSSStyle(\Spreadsheet\Style $pStyle) |
938
|
|
|
{ |
939
|
|
|
// Construct CSS |
940
|
|
|
$css = ''; |
|
|
|
|
941
|
|
|
|
942
|
|
|
// Create CSS |
943
|
|
|
$css = array_merge( |
944
|
|
|
$this->createCSSStyleAlignment($pStyle->getAlignment()), |
945
|
|
|
$this->createCSSStyleBorders($pStyle->getBorders()), |
946
|
|
|
$this->createCSSStyleFont($pStyle->getFont()), |
947
|
|
|
$this->createCSSStyleFill($pStyle->getFill()) |
948
|
|
|
); |
949
|
|
|
|
950
|
|
|
// Return |
951
|
|
|
return $css; |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
/** |
955
|
|
|
* Create CSS style (\Spreadsheet\Style\Alignment) |
956
|
|
|
* |
957
|
|
|
* @param \Spreadsheet\Style\Alignment $pStyle \Spreadsheet\Style\Alignment |
958
|
|
|
* @return array |
959
|
|
|
*/ |
960
|
|
|
private function createCSSStyleAlignment(\Spreadsheet\Style\Alignment $pStyle) |
961
|
|
|
{ |
962
|
|
|
// Construct CSS |
963
|
|
|
$css = array(); |
964
|
|
|
|
965
|
|
|
// Create CSS |
966
|
|
|
$css['vertical-align'] = $this->mapVAlign($pStyle->getVertical()); |
967
|
|
|
if ($textAlign = $this->mapHAlign($pStyle->getHorizontal())) { |
968
|
|
|
$css['text-align'] = $textAlign; |
969
|
|
|
if (in_array($textAlign, array('left', 'right'))) { |
970
|
|
|
$css['padding-'.$textAlign] = (string)((int)$pStyle->getIndent() * 9).'px'; |
971
|
|
|
} |
972
|
|
|
} |
973
|
|
|
|
974
|
|
|
return $css; |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* Create CSS style (\Spreadsheet\Style\Font) |
979
|
|
|
* |
980
|
|
|
* @param \Spreadsheet\Style\Font $pStyle \Spreadsheet\Style\Font |
981
|
|
|
* @return array |
982
|
|
|
*/ |
983
|
|
|
private function createCSSStyleFont(\Spreadsheet\Style\Font $pStyle) |
984
|
|
|
{ |
985
|
|
|
// Construct CSS |
986
|
|
|
$css = array(); |
987
|
|
|
|
988
|
|
|
// Create CSS |
989
|
|
|
if ($pStyle->getBold()) { |
990
|
|
|
$css['font-weight'] = 'bold'; |
991
|
|
|
} |
992
|
|
|
if ($pStyle->getUnderline() != \Spreadsheet\Style\Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) { |
993
|
|
|
$css['text-decoration'] = 'underline line-through'; |
994
|
|
|
} elseif ($pStyle->getUnderline() != \Spreadsheet\Style\Font::UNDERLINE_NONE) { |
995
|
|
|
$css['text-decoration'] = 'underline'; |
996
|
|
|
} elseif ($pStyle->getStrikethrough()) { |
997
|
|
|
$css['text-decoration'] = 'line-through'; |
998
|
|
|
} |
999
|
|
|
if ($pStyle->getItalic()) { |
1000
|
|
|
$css['font-style'] = 'italic'; |
1001
|
|
|
} |
1002
|
|
|
|
1003
|
|
|
$css['color'] = '#' . $pStyle->getColor()->getRGB(); |
1004
|
|
|
$css['font-family'] = '\'' . $pStyle->getName() . '\''; |
1005
|
|
|
$css['font-size'] = $pStyle->getSize() . 'pt'; |
1006
|
|
|
|
1007
|
|
|
return $css; |
1008
|
|
|
} |
1009
|
|
|
|
1010
|
|
|
/** |
1011
|
|
|
* Create CSS style (\Spreadsheet\Style\Borders) |
1012
|
|
|
* |
1013
|
|
|
* @param \Spreadsheet\Style\Borders $pStyle \Spreadsheet\Style\Borders |
1014
|
|
|
* @return array |
1015
|
|
|
*/ |
1016
|
|
|
private function createCSSStyleBorders(\Spreadsheet\Style\Borders $pStyle) |
1017
|
|
|
{ |
1018
|
|
|
// Construct CSS |
1019
|
|
|
$css = array(); |
1020
|
|
|
|
1021
|
|
|
// Create CSS |
1022
|
|
|
$css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom()); |
1023
|
|
|
$css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop()); |
1024
|
|
|
$css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft()); |
1025
|
|
|
$css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight()); |
1026
|
|
|
|
1027
|
|
|
return $css; |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
/** |
1031
|
|
|
* Create CSS style (\Spreadsheet\Style\Border) |
1032
|
|
|
* |
1033
|
|
|
* @param \Spreadsheet\Style\Border $pStyle \Spreadsheet\Style\Border |
1034
|
|
|
* @return string |
1035
|
|
|
*/ |
1036
|
|
|
private function createCSSStyleBorder(\Spreadsheet\Style\Border $pStyle) |
1037
|
|
|
{ |
1038
|
|
|
// Create CSS |
|
|
|
|
1039
|
|
|
// $css = $this->mapBorderStyle($pStyle->getBorderStyle()) . ' #' . $pStyle->getColor()->getRGB(); |
1040
|
|
|
// Create CSS - add !important to non-none border styles for merged cells |
1041
|
|
|
$borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle()); |
1042
|
|
|
$css = $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important'); |
1043
|
|
|
|
1044
|
|
|
return $css; |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
/** |
1048
|
|
|
* Create CSS style (\Spreadsheet\Style\Fill) |
1049
|
|
|
* |
1050
|
|
|
* @param \Spreadsheet\Style\Fill $pStyle \Spreadsheet\Style\Fill |
1051
|
|
|
* @return array |
1052
|
|
|
*/ |
1053
|
|
|
private function createCSSStyleFill(\Spreadsheet\Style\Fill $pStyle) |
1054
|
|
|
{ |
1055
|
|
|
// Construct HTML |
1056
|
|
|
$css = array(); |
1057
|
|
|
|
1058
|
|
|
// Create CSS |
1059
|
|
|
$value = $pStyle->getFillType() == \Spreadsheet\Style\Fill::FILL_NONE ? |
1060
|
|
|
'white' : '#' . $pStyle->getStartColor()->getRGB(); |
1061
|
|
|
$css['background-color'] = $value; |
1062
|
|
|
|
1063
|
|
|
return $css; |
1064
|
|
|
} |
1065
|
|
|
|
1066
|
|
|
/** |
1067
|
|
|
* Generate HTML footer |
1068
|
|
|
*/ |
1069
|
|
|
public function generateHTMLFooter() |
1070
|
|
|
{ |
1071
|
|
|
// Construct HTML |
1072
|
|
|
$html = ''; |
1073
|
|
|
$html .= ' </body>' . PHP_EOL; |
1074
|
|
|
$html .= '</html>' . PHP_EOL; |
1075
|
|
|
|
1076
|
|
|
return $html; |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
|
|
/** |
1080
|
|
|
* Generate table header |
1081
|
|
|
* |
1082
|
|
|
* @param \Spreadsheet\Worksheet $pSheet The worksheet for the table we are writing |
1083
|
|
|
* @return string |
1084
|
|
|
* @throws \Spreadsheet\Writer\Exception |
1085
|
|
|
*/ |
1086
|
|
|
private function generateTableHeader($pSheet) |
1087
|
|
|
{ |
1088
|
|
|
$sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
1089
|
|
|
|
1090
|
|
|
// Construct HTML |
1091
|
|
|
$html = ''; |
1092
|
|
|
$html .= $this->setMargins($pSheet); |
1093
|
|
|
|
1094
|
|
|
if (!$this->useInlineCss) { |
1095
|
|
|
$gridlines = $pSheet->getShowGridlines() ? ' gridlines' : ''; |
1096
|
|
|
$html .= ' <table border="0" cellpadding="0" cellspacing="0" id="sheet' . $sheetIndex . '" class="sheet' . $sheetIndex . $gridlines . '">' . PHP_EOL; |
1097
|
|
|
} else { |
1098
|
|
|
$style = isset($this->cssStyles['table']) ? |
1099
|
|
|
$this->assembleCSS($this->cssStyles['table']) : ''; |
1100
|
|
|
|
1101
|
|
|
if ($this->isPdf && $pSheet->getShowGridlines()) { |
1102
|
|
|
$html .= ' <table border="1" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="1" style="' . $style . '">' . PHP_EOL; |
1103
|
|
|
} else { |
1104
|
|
|
$html .= ' <table border="0" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="0" style="' . $style . '">' . PHP_EOL; |
1105
|
|
|
} |
1106
|
|
|
} |
1107
|
|
|
|
1108
|
|
|
// Write <col> elements |
1109
|
|
|
$highestColumnIndex = \Spreadsheet\Cell::columnIndexFromString($pSheet->getHighestColumn()) - 1; |
1110
|
|
|
$i = -1; |
1111
|
|
|
while ($i++ < $highestColumnIndex) { |
1112
|
|
View Code Duplication |
if (!$this->isPdf) { |
|
|
|
|
1113
|
|
|
if (!$this->useInlineCss) { |
1114
|
|
|
$html .= ' <col class="col' . $i . '">' . PHP_EOL; |
1115
|
|
|
} else { |
1116
|
|
|
$style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ? |
1117
|
|
|
$this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : ''; |
1118
|
|
|
$html .= ' <col style="' . $style . '">' . PHP_EOL; |
1119
|
|
|
} |
1120
|
|
|
} |
1121
|
|
|
} |
1122
|
|
|
|
1123
|
|
|
return $html; |
1124
|
|
|
} |
1125
|
|
|
|
1126
|
|
|
/** |
1127
|
|
|
* Generate table footer |
1128
|
|
|
* |
1129
|
|
|
* @throws \Spreadsheet\Writer\Exception |
1130
|
|
|
*/ |
1131
|
|
|
private function generateTableFooter() |
1132
|
|
|
{ |
1133
|
|
|
$html = ' </table>' . PHP_EOL; |
1134
|
|
|
|
1135
|
|
|
return $html; |
1136
|
|
|
} |
1137
|
|
|
|
1138
|
|
|
/** |
1139
|
|
|
* Generate row |
1140
|
|
|
* |
1141
|
|
|
* @param \Spreadsheet\Worksheet $pSheet \Spreadsheet\Worksheet |
1142
|
|
|
* @param array $pValues Array containing cells in a row |
1143
|
|
|
* @param int $pRow Row number (0-based) |
1144
|
|
|
* @return string |
1145
|
|
|
* @throws \Spreadsheet\Writer\Exception |
1146
|
|
|
*/ |
1147
|
|
|
private function generateRow(\Spreadsheet\Worksheet $pSheet, $pValues = null, $pRow = 0, $cellType = 'td') |
1148
|
|
|
{ |
1149
|
|
|
if (is_array($pValues)) { |
1150
|
|
|
// Construct HTML |
1151
|
|
|
$html = ''; |
1152
|
|
|
|
1153
|
|
|
// Sheet index |
1154
|
|
|
$sheetIndex = $pSheet->getParent()->getIndex($pSheet); |
1155
|
|
|
|
1156
|
|
|
// DomPDF and breaks |
1157
|
|
|
if ($this->isPdf && count($pSheet->getBreaks()) > 0) { |
1158
|
|
|
$breaks = $pSheet->getBreaks(); |
1159
|
|
|
|
1160
|
|
|
// check if a break is needed before this row |
1161
|
|
|
if (isset($breaks['A' . $pRow])) { |
1162
|
|
|
// close table: </table> |
1163
|
|
|
$html .= $this->generateTableFooter(); |
1164
|
|
|
|
1165
|
|
|
// insert page break |
1166
|
|
|
$html .= '<div style="page-break-before:always" />'; |
1167
|
|
|
|
1168
|
|
|
// open table again: <table> + <col> etc. |
1169
|
|
|
$html .= $this->generateTableHeader($pSheet); |
1170
|
|
|
} |
1171
|
|
|
} |
1172
|
|
|
|
1173
|
|
|
// Write row start |
1174
|
|
View Code Duplication |
if (!$this->useInlineCss) { |
|
|
|
|
1175
|
|
|
$html .= ' <tr class="row' . $pRow . '">' . PHP_EOL; |
1176
|
|
|
} else { |
1177
|
|
|
$style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) |
1178
|
|
|
? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : ''; |
1179
|
|
|
|
1180
|
|
|
$html .= ' <tr style="' . $style . '">' . PHP_EOL; |
1181
|
|
|
} |
1182
|
|
|
|
1183
|
|
|
// Write cells |
1184
|
|
|
$colNum = 0; |
1185
|
|
|
foreach ($pValues as $cellAddress) { |
1186
|
|
|
$cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : ''; |
1187
|
|
|
$coordinate = \Spreadsheet\Cell::stringFromColumnIndex($colNum) . ($pRow + 1); |
1188
|
|
|
if (!$this->useInlineCss) { |
1189
|
|
|
$cssClass = ''; |
|
|
|
|
1190
|
|
|
$cssClass = 'column' . $colNum; |
1191
|
|
|
} else { |
1192
|
|
|
$cssClass = array(); |
1193
|
|
|
if ($cellType == 'th') { |
1194
|
|
View Code Duplication |
if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) { |
|
|
|
|
1195
|
|
|
$this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum]; |
1196
|
|
|
} |
1197
|
|
View Code Duplication |
} else { |
|
|
|
|
1198
|
|
|
if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) { |
1199
|
|
|
$this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum]; |
1200
|
|
|
} |
1201
|
|
|
} |
1202
|
|
|
} |
1203
|
|
|
$colSpan = 1; |
1204
|
|
|
$rowSpan = 1; |
1205
|
|
|
|
1206
|
|
|
// initialize |
1207
|
|
|
$cellData = ' '; |
1208
|
|
|
|
1209
|
|
|
// \Spreadsheet\Cell |
1210
|
|
|
if ($cell instanceof \Spreadsheet\Cell) { |
|
|
|
|
1211
|
|
|
$cellData = ''; |
1212
|
|
|
if (is_null($cell->getParent())) { |
1213
|
|
|
$cell->attach($pSheet); |
1214
|
|
|
} |
1215
|
|
|
// Value |
1216
|
|
|
if ($cell->getValue() instanceof \Spreadsheet\RichText) { |
|
|
|
|
1217
|
|
|
// Loop through rich text elements |
1218
|
|
|
$elements = $cell->getValue()->getRichTextElements(); |
1219
|
|
|
foreach ($elements as $element) { |
1220
|
|
|
// Rich text start? |
1221
|
|
|
if ($element instanceof \Spreadsheet\RichText\Run) { |
|
|
|
|
1222
|
|
|
$cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">'; |
1223
|
|
|
|
1224
|
|
|
if ($element->getFont()->getSuperScript()) { |
1225
|
|
|
$cellData .= '<sup>'; |
1226
|
|
|
} elseif ($element->getFont()->getSubScript()) { |
1227
|
|
|
$cellData .= '<sub>'; |
1228
|
|
|
} |
1229
|
|
|
} |
1230
|
|
|
|
1231
|
|
|
// Convert UTF8 data to PCDATA |
1232
|
|
|
$cellText = $element->getText(); |
1233
|
|
|
$cellData .= htmlspecialchars($cellText); |
1234
|
|
|
|
1235
|
|
|
if ($element instanceof \Spreadsheet\RichText\Run) { |
|
|
|
|
1236
|
|
|
if ($element->getFont()->getSuperScript()) { |
1237
|
|
|
$cellData .= '</sup>'; |
1238
|
|
|
} elseif ($element->getFont()->getSubScript()) { |
1239
|
|
|
$cellData .= '</sub>'; |
1240
|
|
|
} |
1241
|
|
|
|
1242
|
|
|
$cellData .= '</span>'; |
1243
|
|
|
} |
1244
|
|
|
} |
1245
|
|
|
} else { |
1246
|
|
|
if ($this->preCalculateFormulas) { |
1247
|
|
|
$cellData = \Spreadsheet\Style\NumberFormat::toFormattedString( |
1248
|
|
|
$cell->getCalculatedValue(), |
1249
|
|
|
$pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
1250
|
|
|
array($this, 'formatColor') |
1251
|
|
|
); |
1252
|
|
|
} else { |
1253
|
|
|
$cellData = \Spreadsheet\Style\NumberFormat::toFormattedString( |
1254
|
|
|
$cell->getValue(), |
1255
|
|
|
$pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(), |
1256
|
|
|
array($this, 'formatColor') |
1257
|
|
|
); |
1258
|
|
|
} |
1259
|
|
|
$cellData = htmlspecialchars($cellData); |
1260
|
|
|
if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperScript()) { |
1261
|
|
|
$cellData = '<sup>'.$cellData.'</sup>'; |
1262
|
|
|
} elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubScript()) { |
1263
|
|
|
$cellData = '<sub>'.$cellData.'</sub>'; |
1264
|
|
|
} |
1265
|
|
|
} |
1266
|
|
|
|
1267
|
|
|
// Converts the cell content so that spaces occuring at beginning of each new line are replaced by |
1268
|
|
|
// Example: " Hello\n to the world" is converted to " Hello\n to the world" |
1269
|
|
|
$cellData = preg_replace("/(?m)(?:^|\\G) /", ' ', $cellData); |
1270
|
|
|
|
1271
|
|
|
// convert newline "\n" to '<br>' |
1272
|
|
|
$cellData = nl2br($cellData); |
1273
|
|
|
|
1274
|
|
|
// Extend CSS class? |
1275
|
|
|
if (!$this->useInlineCss) { |
1276
|
|
|
$cssClass .= ' style' . $cell->getXfIndex(); |
1277
|
|
|
$cssClass .= ' ' . $cell->getDataType(); |
1278
|
|
|
} else { |
1279
|
|
|
if ($cellType == 'th') { |
1280
|
|
View Code Duplication |
if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) { |
|
|
|
|
1281
|
|
|
$cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]); |
1282
|
|
|
} |
1283
|
|
View Code Duplication |
} else { |
|
|
|
|
1284
|
|
|
if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) { |
1285
|
|
|
$cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]); |
1286
|
|
|
} |
1287
|
|
|
} |
1288
|
|
|
|
1289
|
|
|
// General horizontal alignment: Actual horizontal alignment depends on dataType |
1290
|
|
|
$sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex()); |
1291
|
|
|
if ($sharedStyle->getAlignment()->getHorizontal() == \Spreadsheet\Style\Alignment::HORIZONTAL_GENERAL |
1292
|
|
|
&& isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])) { |
1293
|
|
|
$cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align']; |
1294
|
|
|
} |
1295
|
|
|
} |
1296
|
|
|
} |
1297
|
|
|
|
1298
|
|
|
// Hyperlink? |
1299
|
|
|
if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) { |
1300
|
|
|
$cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>'; |
1301
|
|
|
} |
1302
|
|
|
|
1303
|
|
|
// Should the cell be written or is it swallowed by a rowspan or colspan? |
1304
|
|
|
$writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]) |
1305
|
|
|
&& $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]); |
1306
|
|
|
|
1307
|
|
|
// Colspan and Rowspan |
1308
|
|
|
$colspan = 1; |
|
|
|
|
1309
|
|
|
$rowspan = 1; |
|
|
|
|
1310
|
|
|
if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) { |
1311
|
|
|
$spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]; |
1312
|
|
|
$rowSpan = $spans['rowspan']; |
1313
|
|
|
$colSpan = $spans['colspan']; |
1314
|
|
|
|
1315
|
|
|
// Also apply style from last cell in merge to fix borders - |
1316
|
|
|
// relies on !important for non-none border declarations in createCSSStyleBorder |
1317
|
|
|
$endCellCoord = \Spreadsheet\Cell::stringFromColumnIndex($colNum + $colSpan - 1) . ($pRow + $rowSpan); |
1318
|
|
|
if (!$this->useInlineCss) { |
1319
|
|
|
$cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex(); |
1320
|
|
|
} |
1321
|
|
|
} |
1322
|
|
|
|
1323
|
|
|
// Write |
1324
|
|
|
if ($writeCell) { |
1325
|
|
|
// Column start |
1326
|
|
|
$html .= ' <' . $cellType; |
1327
|
|
|
if (!$this->useInlineCss) { |
1328
|
|
|
$html .= ' class="' . $cssClass . '"'; |
1329
|
|
|
} else { |
1330
|
|
|
//** Necessary redundant code for the sake of Spreadsheet_Writer_PDF ** |
1331
|
|
|
// We must explicitly write the width of the <td> element because TCPDF |
1332
|
|
|
// does not recognize e.g. <col style="width:42pt"> |
1333
|
|
|
$width = 0; |
1334
|
|
|
$i = $colNum - 1; |
1335
|
|
|
$e = $colNum + $colSpan - 1; |
1336
|
|
|
while ($i++ < $e) { |
1337
|
|
|
if (isset($this->columnWidths[$sheetIndex][$i])) { |
1338
|
|
|
$width += $this->columnWidths[$sheetIndex][$i]; |
1339
|
|
|
} |
1340
|
|
|
} |
1341
|
|
|
$cssClass['width'] = $width . 'pt'; |
1342
|
|
|
|
1343
|
|
|
// We must also explicitly write the height of the <td> element because TCPDF |
1344
|
|
|
// does not recognize e.g. <tr style="height:50pt"> |
1345
|
|
|
if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) { |
1346
|
|
|
$height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height']; |
1347
|
|
|
$cssClass['height'] = $height; |
1348
|
|
|
} |
1349
|
|
|
//** end of redundant code ** |
1350
|
|
|
|
1351
|
|
|
$html .= ' style="' . $this->assembleCSS($cssClass) . '"'; |
1352
|
|
|
} |
1353
|
|
|
if ($colSpan > 1) { |
1354
|
|
|
$html .= ' colspan="' . $colSpan . '"'; |
1355
|
|
|
} |
1356
|
|
|
if ($rowSpan > 1) { |
1357
|
|
|
$html .= ' rowspan="' . $rowSpan . '"'; |
1358
|
|
|
} |
1359
|
|
|
$html .= '>'; |
1360
|
|
|
|
1361
|
|
|
// Image? |
1362
|
|
|
$html .= $this->writeImageInCell($pSheet, $coordinate); |
1363
|
|
|
|
1364
|
|
|
// Chart? |
1365
|
|
|
if ($this->includeCharts) { |
1366
|
|
|
$html .= $this->writeChartInCell($pSheet, $coordinate); |
1367
|
|
|
} |
1368
|
|
|
|
1369
|
|
|
// Cell data |
1370
|
|
|
$html .= $cellData; |
1371
|
|
|
|
1372
|
|
|
// Column end |
1373
|
|
|
$html .= '</'.$cellType.'>' . PHP_EOL; |
1374
|
|
|
} |
1375
|
|
|
|
1376
|
|
|
// Next column |
1377
|
|
|
++$colNum; |
1378
|
|
|
} |
1379
|
|
|
|
1380
|
|
|
// Write row end |
1381
|
|
|
$html .= ' </tr>' . PHP_EOL; |
1382
|
|
|
|
1383
|
|
|
// Return |
1384
|
|
|
return $html; |
1385
|
|
|
} else { |
1386
|
|
|
throw new \Spreadsheet\Writer\Exception("Invalid parameters passed."); |
1387
|
|
|
} |
1388
|
|
|
} |
1389
|
|
|
|
1390
|
|
|
/** |
1391
|
|
|
* Takes array where of CSS properties / values and converts to CSS string |
1392
|
|
|
* |
1393
|
|
|
* @param array |
1394
|
|
|
* @return string |
1395
|
|
|
*/ |
1396
|
|
|
private function assembleCSS($pValue = array()) |
1397
|
|
|
{ |
1398
|
|
|
$pairs = array(); |
1399
|
|
|
foreach ($pValue as $property => $value) { |
1400
|
|
|
$pairs[] = $property . ':' . $value; |
1401
|
|
|
} |
1402
|
|
|
$string = implode('; ', $pairs); |
1403
|
|
|
|
1404
|
|
|
return $string; |
1405
|
|
|
} |
1406
|
|
|
|
1407
|
|
|
/** |
1408
|
|
|
* Get images root |
1409
|
|
|
* |
1410
|
|
|
* @return string |
1411
|
|
|
*/ |
1412
|
|
|
public function getImagesRoot() |
1413
|
|
|
{ |
1414
|
|
|
return $this->imagesRoot; |
1415
|
|
|
} |
1416
|
|
|
|
1417
|
|
|
/** |
1418
|
|
|
* Set images root |
1419
|
|
|
* |
1420
|
|
|
* @param string $pValue |
1421
|
|
|
* @return Spreadsheet_Writer_HTML |
1422
|
|
|
*/ |
1423
|
|
|
public function setImagesRoot($pValue = '.') |
1424
|
|
|
{ |
1425
|
|
|
$this->imagesRoot = $pValue; |
1426
|
|
|
return $this; |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
/** |
1430
|
|
|
* Get embed images |
1431
|
|
|
* |
1432
|
|
|
* @return boolean |
1433
|
|
|
*/ |
1434
|
|
|
public function getEmbedImages() |
1435
|
|
|
{ |
1436
|
|
|
return $this->embedImages; |
1437
|
|
|
} |
1438
|
|
|
|
1439
|
|
|
/** |
1440
|
|
|
* Set embed images |
1441
|
|
|
* |
1442
|
|
|
* @param boolean $pValue |
1443
|
|
|
* @return Spreadsheet_Writer_HTML |
1444
|
|
|
*/ |
1445
|
|
|
public function setEmbedImages($pValue = '.') |
1446
|
|
|
{ |
1447
|
|
|
$this->embedImages = $pValue; |
|
|
|
|
1448
|
|
|
return $this; |
1449
|
|
|
} |
1450
|
|
|
|
1451
|
|
|
/** |
1452
|
|
|
* Get use inline CSS? |
1453
|
|
|
* |
1454
|
|
|
* @return boolean |
1455
|
|
|
*/ |
1456
|
|
|
public function getUseInlineCss() |
1457
|
|
|
{ |
1458
|
|
|
return $this->useInlineCss; |
1459
|
|
|
} |
1460
|
|
|
|
1461
|
|
|
/** |
1462
|
|
|
* Set use inline CSS? |
1463
|
|
|
* |
1464
|
|
|
* @param boolean $pValue |
1465
|
|
|
* @return Spreadsheet_Writer_HTML |
1466
|
|
|
*/ |
1467
|
|
|
public function setUseInlineCss($pValue = false) |
1468
|
|
|
{ |
1469
|
|
|
$this->useInlineCss = $pValue; |
1470
|
|
|
return $this; |
1471
|
|
|
} |
1472
|
|
|
|
1473
|
|
|
/** |
1474
|
|
|
* Add color to formatted string as inline style |
1475
|
|
|
* |
1476
|
|
|
* @param string $pValue Plain formatted value without color |
1477
|
|
|
* @param string $pFormat Format code |
1478
|
|
|
* @return string |
1479
|
|
|
*/ |
1480
|
|
|
public function formatColor($pValue, $pFormat) |
1481
|
|
|
{ |
1482
|
|
|
// Color information, e.g. [Red] is always at the beginning |
1483
|
|
|
$color = null; // initialize |
1484
|
|
|
$matches = array(); |
1485
|
|
|
|
1486
|
|
|
$color_regex = '/^\\[[a-zA-Z]+\\]/'; |
1487
|
|
|
if (preg_match($color_regex, $pFormat, $matches)) { |
1488
|
|
|
$color = str_replace('[', '', $matches[0]); |
1489
|
|
|
$color = str_replace(']', '', $color); |
1490
|
|
|
$color = strtolower($color); |
1491
|
|
|
} |
1492
|
|
|
|
1493
|
|
|
// convert to PCDATA |
1494
|
|
|
$value = htmlspecialchars($pValue); |
1495
|
|
|
|
1496
|
|
|
// color span tag |
1497
|
|
|
if ($color !== null) { |
1498
|
|
|
$value = '<span style="color:' . $color . '">' . $value . '</span>'; |
1499
|
|
|
} |
1500
|
|
|
|
1501
|
|
|
return $value; |
1502
|
|
|
} |
1503
|
|
|
|
1504
|
|
|
/** |
1505
|
|
|
* Calculate information about HTML colspan and rowspan which is not always the same as Excel's |
1506
|
|
|
*/ |
1507
|
|
|
private function calculateSpans() |
1508
|
|
|
{ |
1509
|
|
|
// Identify all cells that should be omitted in HTML due to cell merge. |
1510
|
|
|
// In HTML only the upper-left cell should be written and it should have |
1511
|
|
|
// appropriate rowspan / colspan attribute |
1512
|
|
|
$sheetIndexes = $this->sheetIndex !== null ? |
1513
|
|
|
array($this->sheetIndex) : range(0, $this->spreadsheet->getSheetCount() - 1); |
1514
|
|
|
|
1515
|
|
|
foreach ($sheetIndexes as $sheetIndex) { |
1516
|
|
|
$sheet = $this->spreadsheet->getSheet($sheetIndex); |
1517
|
|
|
|
1518
|
|
|
$candidateSpannedRow = array(); |
1519
|
|
|
|
1520
|
|
|
// loop through all Excel merged cells |
1521
|
|
|
foreach ($sheet->getMergeCells() as $cells) { |
1522
|
|
|
list($cells,) = \Spreadsheet\Cell::splitRange($cells); |
1523
|
|
|
$first = $cells[0]; |
1524
|
|
|
$last = $cells[1]; |
1525
|
|
|
|
1526
|
|
|
list($fc, $fr) = \Spreadsheet\Cell::coordinateFromString($first); |
1527
|
|
|
$fc = \Spreadsheet\Cell::columnIndexFromString($fc) - 1; |
1528
|
|
|
|
1529
|
|
|
list($lc, $lr) = \Spreadsheet\Cell::coordinateFromString($last); |
1530
|
|
|
$lc = \Spreadsheet\Cell::columnIndexFromString($lc) - 1; |
1531
|
|
|
|
1532
|
|
|
// loop through the individual cells in the individual merge |
1533
|
|
|
$r = $fr - 1; |
1534
|
|
|
while ($r++ < $lr) { |
1535
|
|
|
// also, flag this row as a HTML row that is candidate to be omitted |
1536
|
|
|
$candidateSpannedRow[$r] = $r; |
1537
|
|
|
|
1538
|
|
|
$c = $fc - 1; |
1539
|
|
|
while ($c++ < $lc) { |
1540
|
|
|
if (!($c == $fc && $r == $fr)) { |
1541
|
|
|
// not the upper-left cell (should not be written in HTML) |
1542
|
|
|
$this->isSpannedCell[$sheetIndex][$r][$c] = array( |
1543
|
|
|
'baseCell' => array($fr, $fc), |
1544
|
|
|
); |
1545
|
|
|
} else { |
1546
|
|
|
// upper-left is the base cell that should hold the colspan/rowspan attribute |
1547
|
|
|
$this->isBaseCell[$sheetIndex][$r][$c] = array( |
1548
|
|
|
'xlrowspan' => $lr - $fr + 1, // Excel rowspan |
1549
|
|
|
'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change |
1550
|
|
|
'xlcolspan' => $lc - $fc + 1, // Excel colspan |
1551
|
|
|
'colspan' => $lc - $fc + 1, // HTML colspan, value may change |
1552
|
|
|
); |
1553
|
|
|
} |
1554
|
|
|
} |
1555
|
|
|
} |
1556
|
|
|
} |
1557
|
|
|
|
1558
|
|
|
// Identify which rows should be omitted in HTML. These are the rows where all the cells |
1559
|
|
|
// participate in a merge and the where base cells are somewhere above. |
1560
|
|
|
$countColumns = \Spreadsheet\Cell::columnIndexFromString($sheet->getHighestColumn()); |
1561
|
|
|
foreach ($candidateSpannedRow as $rowIndex) { |
1562
|
|
|
if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) { |
1563
|
|
|
if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) { |
1564
|
|
|
$this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex; |
1565
|
|
|
}; |
1566
|
|
|
} |
1567
|
|
|
} |
1568
|
|
|
|
1569
|
|
|
// For each of the omitted rows we found above, the affected rowspans should be subtracted by 1 |
1570
|
|
|
if (isset($this->isSpannedRow[$sheetIndex])) { |
1571
|
|
|
foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) { |
1572
|
|
|
$adjustedBaseCells = array(); |
1573
|
|
|
$c = -1; |
1574
|
|
|
$e = $countColumns - 1; |
1575
|
|
|
while ($c++ < $e) { |
1576
|
|
|
$baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell']; |
1577
|
|
|
|
1578
|
|
|
if (!in_array($baseCell, $adjustedBaseCells)) { |
1579
|
|
|
// subtract rowspan by 1 |
1580
|
|
|
--$this->isBaseCell[$sheetIndex][ $baseCell[0] ][ $baseCell[1] ]['rowspan']; |
1581
|
|
|
$adjustedBaseCells[] = $baseCell; |
1582
|
|
|
} |
1583
|
|
|
} |
1584
|
|
|
} |
1585
|
|
|
} |
1586
|
|
|
|
1587
|
|
|
// TODO: Same for columns |
1588
|
|
|
} |
1589
|
|
|
|
1590
|
|
|
// We have calculated the spans |
1591
|
|
|
$this->spansAreCalculated = true; |
1592
|
|
|
} |
1593
|
|
|
|
1594
|
|
|
private function setMargins(\Spreadsheet\Worksheet $pSheet) |
1595
|
|
|
{ |
1596
|
|
|
$htmlPage = '@page { '; |
1597
|
|
|
$htmlBody = 'body { '; |
1598
|
|
|
|
1599
|
|
|
$left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; '; |
1600
|
|
|
$htmlPage .= 'margin-left: ' . $left; |
1601
|
|
|
$htmlBody .= 'margin-left: ' . $left; |
1602
|
|
|
$right = StringHelper::formatNumber($pSheet->getPageMargins()->getRight()) . 'in; '; |
1603
|
|
|
$htmlPage .= 'margin-right: ' . $right; |
1604
|
|
|
$htmlBody .= 'margin-right: ' . $right; |
1605
|
|
|
$top = StringHelper::formatNumber($pSheet->getPageMargins()->getTop()) . 'in; '; |
1606
|
|
|
$htmlPage .= 'margin-top: ' . $top; |
1607
|
|
|
$htmlBody .= 'margin-top: ' . $top; |
1608
|
|
|
$bottom = StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()) . 'in; '; |
1609
|
|
|
$htmlPage .= 'margin-bottom: ' . $bottom; |
1610
|
|
|
$htmlBody .= 'margin-bottom: ' . $bottom; |
1611
|
|
|
|
1612
|
|
|
$htmlPage .= "}\n"; |
1613
|
|
|
$htmlBody .= "}\n"; |
1614
|
|
|
|
1615
|
|
|
return "<style>\n" . $htmlPage . $htmlBody . "</style>\n"; |
1616
|
|
|
} |
1617
|
|
|
} |
1618
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.