PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | libraries/simplexlsx.class.php +208 -190 2.0.4 → 3.4 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2 /**
3 - * Excel 2007-2019/Office 365 Reader Class
3 + * Excel 2007-2019/Office 365 Reader class
4 4 *
5 - * Based on SimpleXLSX v1.0.16 by Sergey Shuchkin.
5 + * Based on SimpleXLSX v1.1.19 by Sergey Shuchkin.
6 6 * @link https://github.com/shuchkin/simplexlsx/
7 7 *
8 8 * @package TablePress
9 9 * @subpackage Import
@@ -15,9 +15,10 @@
15 15
16 16 use SimpleXMLElement;
17 17
18 18 /**
19 - * PHP Excel 2007-2013/Office 365 Reader Class
19 + * PHP Excel 2007-2013/Office 365 Reader class
20 + *
20 21 * @package TablePress
21 22 * @subpackage Import
22 23 * @author Sergey Shuchkin, Tobias Bäthge
23 24 * @since 1.1.0
@@ -78,18 +79,18 @@
78 79 public $activeSheet = 0;
79 80 public $rowsExReader;
80 81
81 82 /* @var SimpleXMLElement[] $sheets */
82 - protected $sheets;
83 - protected $sheetNames = [];
84 - protected $sheetFiles = [];
83 + public $sheets;
84 + public $sheetFiles = [];
85 + public $sheetMetaData = [];
86 + public $sheetRels = [];
85 87 // scheme
86 88 public $styles;
87 - protected $hyperlinks;
88 89 /* @var array[] $package */
89 - protected $package;
90 - protected $sharedstrings;
91 - protected $date1904 = 0;
90 + public $package;
91 + public $sharedstrings;
92 + public $date1904 = 0;
92 93
93 94
94 95 /*
95 96 private $date_formats = array(
@@ -128,10 +129,10 @@
128 129 0x2c => '$%1.2f', //"$#,##0.00;($#,##0.00)",
129 130 0x30 => '%1.0f'); //"##0.0E0";
130 131 // }}}
131 132 */
132 - protected $errno = 0;
133 - protected $error = false;
133 + public $errno = 0;
134 + public $error = false;
134 135 /**
135 136 * @var false|SimpleXMLElement
136 137 */
137 138 public $theme;
@@ -148,20 +149,20 @@
148 149 'size' => 0,
149 150 'comment' => '',
150 151 'entries' => []
151 152 ];
152 - if ($filename && $this->_unzip($filename, $is_data)) {
153 - $this->_parse();
153 + if ($filename && $this->unzip($filename, $is_data)) {
154 + $this->parseEntries();
154 155 }
155 156 }
156 157
157 - protected function _unzip($filename, $is_data = false)
158 + public function unzip($filename, $is_data = false)
158 159 {
159 160
160 161 if ($is_data) {
161 162 $this->package['filename'] = 'default.xlsx';
162 163 $this->package['mtime'] = time();
163 - $this->package['size'] = $this->_strlen($filename);
164 + $this->package['size'] = self::strlen($filename);
164 165
165 166 $vZ = $filename;
166 167 } else {
167 168 if (!is_readable($filename)) {
@@ -229,25 +230,44 @@
229 230 $mF = $aP['EFL'];
230 231
231 232 // Special case : value block after the compressed data
232 233 if ($aP['GPF'] & 0x0008) {
233 - $aP1 = unpack('V1CRC/V1CS/V1UCS', $this->_substr($vZ, -12));
234 + // Signed ZIP64 descriptor:
235 + // signature + CRC32 + 64-bit compressed size + 64-bit uncompressed size
236 + if (self::substr($vZ, -24, 4) === "\x50\x4b\x07\x08") {
237 + $aP1 = unpack(
238 + 'V1CRC/V1CSLow/V1CSHigh/V1UCSLow/V1UCSHigh',
239 + self::substr($vZ, -20)
240 + );
234 241
235 - $aP['CRC'] = $aP1['CRC'];
236 - $aP['CS'] = $aP1['CS'];
237 - $aP['UCS'] = $aP1['UCS'];
238 - // 2013-08-10
239 - $vZ = $this->_substr($vZ, 0, -12);
240 - if ($this->_substr($vZ, -4) === "\x50\x4b\x07\x08") {
241 - $vZ = $this->_substr($vZ, 0, -4);
242 - }
242 + if ((int)$aP1['CSHigh'] !== 0 || (int)$aP1['UCSHigh'] !== 0) {
243 + $aI['E'] = 6;
244 + $aI['EM'] = 'ZIP64 entry is too large to process.';
245 + }
246 +
247 + $aP['CRC'] = $aP1['CRC'];
248 + $aP['CS'] = $aP1['CSLow'];
249 + $aP['UCS'] = $aP1['UCSLow'];
250 + $vZ = self::substr($vZ, 0, -24);
251 + } else {
252 + $aP1 = unpack('V1CRC/V1CS/V1UCS', self::substr($vZ, -12));
253 +
254 + $aP['CRC'] = $aP1['CRC'];
255 + $aP['CS'] = $aP1['CS'];
256 + $aP['UCS'] = $aP1['UCS'];
257 + // 2013-08-10
258 + $vZ = self::substr($vZ, 0, -12);
259 + if (self::substr($vZ, -4) === "\x50\x4b\x07\x08") {
260 + $vZ = self::substr($vZ, 0, -4);
261 + }
262 + }
243 263 }
244 264
245 265 // Getting stored filename
246 - $aI['N'] = $this->_substr($vZ, 26, $nF);
266 + $aI['N'] = self::substr($vZ, 26, $nF);
247 267 $aI['N'] = str_replace('\\', '/', $aI['N']);
248 268
249 - if ($this->_substr($aI['N'], -1) === '/') {
269 + if (self::substr($aI['N'], -1) === '/') {
250 270 // is a directory entry - will be skipped
251 271 continue;
252 272 }
253 273
@@ -255,11 +275,11 @@
255 275 $aI['P'] = dirname($aI['N']);
256 276 $aI['P'] = ($aI['P'] === '.') ? '' : $aI['P'];
257 277 $aI['N'] = basename($aI['N']);
258 278
259 - $vZ = $this->_substr($vZ, 26 + $nF + $mF);
279 + $vZ = self::substr($vZ, 26 + $nF + $mF);
260 280
261 - if ($this->_strlen($vZ) !== (int)$aP['CS']) { // check only if availabled
281 + if ($aP['CS'] > 0 && (self::strlen($vZ) !== (int)$aP['CS'])) { // check only if availabled
262 282 $aI['E'] = 1;
263 283 $aI['EM'] = 'Compressed size is not equal with the value in header information.';
264 284 }
265 285 // } elseif ( $bE ) {
@@ -326,12 +346,8 @@
326 346
327 347 return true;
328 348 }
329 349
330 - protected function _strlen($str)
331 - {
332 - return (ini_get('mbstring.func_overload') & 2) ? mb_strlen($str, '8bit') : strlen($str);
333 - }
334 350
335 351 public function error($num = null, $str = null)
336 352 {
337 353 if ($num) {
@@ -344,15 +360,10 @@
344 360
345 361 return $this->error;
346 362 }
347 363
348 - protected function _substr($str, $start, $length = null)
364 + public function parseEntries()
349 365 {
350 - return (ini_get('mbstring.func_overload') & 2) ? mb_substr($str, $start, ($length === null) ? mb_strlen($str, '8bit') : $length, '8bit') : substr($str, $start, ($length === null) ? strlen($str) : $length);
351 - }
352 -
353 - protected function _parse()
354 - {
355 366 // Document data holders
356 367 $this->sharedstrings = [];
357 368 $this->sheets = [];
358 369 // $this->styles = array();
@@ -360,16 +371,22 @@
360 371 // Read relations and search for officeDocument
361 372 if ($relations = $this->getEntryXML('_rels/.rels')) {
362 373 foreach ($relations->Relationship as $rel) {
363 374 $rel_type = basename(trim((string)$rel['Type'])); // officeDocument
364 - $rel_target = $this->_getTarget('', (string)$rel['Target']); // /xl/workbook.xml or xl/workbook.xml
375 + $rel_target = self::getTarget('', (string)$rel['Target']); // /xl/workbook.xml or xl/workbook.xml
365 376
366 - if ($rel_type === 'officeDocument' && $workbook = $this->getEntryXML($rel_target)) {
377 + if ($rel_type === 'officeDocument'
378 + && $workbook = $this->getEntryXML($rel_target)
379 + ) {
367 380 $index_rId = []; // [0 => rId1]
368 381
369 382 $index = 0;
370 383 foreach ($workbook->sheets->sheet as $s) {
371 - $this->sheetNames[$index] = (string)$s['name'];
384 + $a = [];
385 + foreach ($s->attributes() as $k => $v) {
386 + $a[(string)$k] = (string)$v;
387 + }
388 + $this->sheetMetaData[$index] = $a;
372 389 $index_rId[$index] = (string)$s['id'];
373 390 $index++;
374 391 }
375 392 if ((int)$workbook->workbookPr['date1904'] === 1) {
@@ -380,32 +397,37 @@
380 397 if ($workbookRelations = $this->getEntryXML(dirname($rel_target) . '/_rels/workbook.xml.rels')) {
381 398 // Loop relations for workbook and extract sheets...
382 399 foreach ($workbookRelations->Relationship as $workbookRelation) {
383 400 $wrel_type = basename(trim((string)$workbookRelation['Type'])); // worksheet
384 - $wrel_path = $this->_getTarget(dirname($rel_target), (string)$workbookRelation['Target']);
385 - if (!$this->entryExists($wrel_path)) {
401 + $wrel_target = self::getTarget(dirname($rel_target), (string)$workbookRelation['Target']);
402 + if (!$this->entryExists($wrel_target)) {
386 403 continue;
387 404 }
388 405
389 -
390 406 if ($wrel_type === 'worksheet') { // Sheets
391 - if ($sheet = $this->getEntryXML($wrel_path)) {
407 + if ($sheet = $this->getEntryXML($wrel_target)) {
392 408 $index = array_search((string)$workbookRelation['Id'], $index_rId, true);
393 409 $this->sheets[$index] = $sheet;
394 - $this->sheetFiles[$index] = $wrel_path;
410 + $this->sheetFiles[$index] = $wrel_target;
411 + $srel_d = dirname($wrel_target);
412 + $srel_f = basename($wrel_target);
413 + $srel_file = $srel_d . '/_rels/' . $srel_f . '.rels';
414 + if ($this->entryExists($srel_file)) {
415 + $this->sheetRels[$index] = $this->getEntryXML($srel_file);
416 + }
395 417 }
396 418 } elseif ($wrel_type === 'sharedStrings') {
397 - if ($sharedStrings = $this->getEntryXML($wrel_path)) {
419 + if ($sharedStrings = $this->getEntryXML($wrel_target)) {
398 420 foreach ($sharedStrings->si as $val) {
399 421 if (isset($val->t)) {
400 422 $this->sharedstrings[] = (string)$val->t;
401 423 } elseif (isset($val->r)) {
402 - $this->sharedstrings[] = $this->_parseRichText($val);
424 + $this->sharedstrings[] = self::parseRichText($val);
403 425 }
404 426 }
405 427 }
406 428 } elseif ($wrel_type === 'styles') {
407 - $this->styles = $this->getEntryXML($wrel_path);
429 + $this->styles = $this->getEntryXML($wrel_target);
408 430
409 431 // number formats
410 432 $this->nf = [];
411 433 if (isset($this->styles->numFmts->numFmt)) {
@@ -434,20 +456,24 @@
434 456 $this->cellFormats[] = $x;
435 457 }
436 458 }
437 459 } elseif ($wrel_type === 'theme') {
438 - $this->theme = $this->getEntryXML($wrel_path);
460 + $this->theme = $this->getEntryXML($wrel_target);
439 461 }
440 462 }
441 463
442 - break;
464 +// break;
443 465 }
444 466 // reptile hack :: find active sheet from workbook.xml
445 - foreach ($workbook->bookViews->workbookView as $s) {
446 - if (!empty($s['activeTab'])) {
447 - $this->activeSheet = (int)$s['activeTab'];
467 + if ($workbook->bookViews->workbookView) {
468 + foreach ($workbook->bookViews->workbookView as $v) {
469 + if (!empty($v['activeTab'])) {
470 + $this->activeSheet = (int)$v['activeTab'];
471 + }
448 472 }
449 473 }
474 +
475 + break;
450 476 }
451 477 }
452 478 }
453 479
@@ -470,9 +496,10 @@
470 496 $this->deleteEntry($name); // economy memory
471 497 // dirty remove namespace prefixes and empty rows
472 498 $entry_xml = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $entry_xml); // remove namespaces
473 499 $entry_xml .= ' '; // force run garbage collector
474 - $entry_xml = preg_replace('/[a-zA-Z0-9]+:([a-zA-Z0-9]+="[^"]+")/', '$1', $entry_xml); // remove namespaced attrs
500 + // remove namespaced attrs
501 + $entry_xml = preg_replace('/[a-zA-Z0-9]+:([a-zA-Z0-9]+="[^"]+")/', '$1', $entry_xml);
475 502 $entry_xml .= ' ';
476 503 $entry_xml = preg_replace('/<[a-zA-Z0-9]+:([^>]+)>/', '<$1>', $entry_xml); // fix namespaced openned tags
477 504 $entry_xml .= ' ';
478 505 $entry_xml = preg_replace('/<\/[a-zA-Z0-9]+:([^>]+)>/', '</$1>', $entry_xml); // fix namespaced closed tags
@@ -479,8 +506,9 @@
479 506 $entry_xml .= ' ';
480 507
481 508 if (strpos($name, '/sheet')) { // dirty skip empty rows
482 509 // remove <row...> <c /><c /></row>
510 + $cnt = $cnt2 = $cnt3 = null;
483 511 $entry_xml = preg_replace('/<row[^>]+>\s*(<c[^\/]+\/>\s*)+<\/row>/', '', $entry_xml, -1, $cnt);
484 512 $entry_xml .= ' ';
485 513 // remove <row />
486 514 $entry_xml = preg_replace('/<row[^\/>]*\/>/', '', $entry_xml, -1, $cnt2);
@@ -534,12 +562,12 @@
534 562
535 563 public function getEntryData($name)
536 564 {
537 565 $name = ltrim(str_replace('\\', '/', $name), '/');
538 - $dir = $this->_strtoupper(dirname($name));
539 - $name = $this->_strtoupper(basename($name));
566 + $dir = self::strtoupper(dirname($name));
567 + $name = self::strtoupper(basename($name));
540 568 foreach ($this->package['entries'] as &$entry) {
541 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
569 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
542 570 if ($entry['error']) {
543 571 return false;
544 572 }
545 573 switch ($entry['cm']) {
@@ -566,9 +594,9 @@
566 594 $entry['cm'] = -1;
567 595 if ($entry['data'] === false) {
568 596 $entry['error'] = 2;
569 597 $entry['error_msg'] = 'Decompression of data failed.';
570 - } elseif ($this->_strlen($entry['data']) !== (int)$entry['ucs']) {
598 + } elseif ($entry['ucs'] > 0 && (self::strlen($entry['data']) !== (int)$entry['ucs'])) {
571 599 $entry['error'] = 3;
572 600 $entry['error_msg'] = 'Uncompressed size is not equal with the value in header information.';
573 601 } elseif (crc32($entry['data']) !== $entry['crc']) {
574 602 $entry['error'] = 4;
@@ -586,12 +614,12 @@
586 614 }
587 615 public function deleteEntry($name)
588 616 {
589 617 $name = ltrim(str_replace('\\', '/', $name), '/');
590 - $dir = $this->_strtoupper(dirname($name));
591 - $name = $this->_strtoupper(basename($name));
618 + $dir = self::strtoupper(dirname($name));
619 + $name = self::strtoupper(basename($name));
592 620 foreach ($this->package['entries'] as $k => $entry) {
593 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
621 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
594 622 unset($this->package['entries'][$k]);
595 623 return true;
596 624 }
597 625 }
@@ -597,36 +625,13 @@
597 625 }
598 626 return false;
599 627 }
600 628
601 - protected function _strtoupper($str)
629 + public static function strtoupper($str)
602 630 {
603 631 return (ini_get('mbstring.func_overload') & 2) ? mb_strtoupper($str, '8bit') : strtoupper($str);
604 632 }
605 633
606 - protected function _getTarget($base, $target)
607 - {
608 - $target = trim($target);
609 - if (strpos($target, '/') === 0) {
610 - return $this->_substr($target, 1);
611 - }
612 - $target = ($base ? $base . '/' : '') . $target;
613 - // a/b/../c -> a/c
614 - $parts = explode('/', $target);
615 - $abs = [];
616 - foreach ($parts as $p) {
617 - if ('.' === $p) {
618 - continue;
619 - }
620 - if ('..' === $p) {
621 - array_pop($abs);
622 - } else {
623 - $abs[] = $p;
624 - }
625 - }
626 - return implode('/', $abs);
627 - }
628 -
629 634 /*
630 635 * @param string $name Filename in archive
631 636 * @return SimpleXMLElement|bool
632 637 */
@@ -633,12 +638,12 @@
633 638
634 639 public function entryExists($name)
635 640 {
636 641 // 0.6.6
637 - $dir = $this->_strtoupper(dirname($name));
638 - $name = $this->_strtoupper(basename($name));
642 + $dir = self::strtoupper(dirname($name));
643 + $name = self::strtoupper(basename($name));
639 644 foreach ($this->package['entries'] as $entry) {
640 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
645 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
641 646 return true;
642 647 }
643 648 }
644 649
@@ -644,23 +649,8 @@
644 649
645 650 return false;
646 651 }
647 652
648 - protected function _parseRichText($is = null)
649 - {
650 - $value = [];
651 -
652 - if (isset($is->t)) {
653 - $value[] = (string)$is->t;
654 - } elseif (isset($is->r)) {
655 - foreach ($is->r as $run) {
656 - $value[] = (string)$run->t;
657 - }
658 - }
659 -
660 - return implode('', $value);
661 - }
662 -
663 653 public static function parseFile($filename, $debug = false)
664 654 {
665 655 return self::parse($filename, false, $debug);
666 656 }
@@ -668,10 +658,10 @@
668 658 public static function parse($filename, $is_data = false, $debug = false)
669 659 {
670 660 $xlsx = new self();
671 661 $xlsx->debug = $debug;
672 - if ($xlsx->_unzip($filename, $is_data)) {
673 - $xlsx->_parse();
662 + if ($xlsx->unzip($filename, $is_data)) {
663 + $xlsx->parseEntries();
674 664 }
675 665 if ($xlsx->success()) {
676 666 return $xlsx;
677 667 }
@@ -715,49 +705,10 @@
715 705
716 706
717 707 public function worksheet($worksheetIndex = 0)
718 708 {
719 -
720 -
721 709 if (isset($this->sheets[$worksheetIndex])) {
722 - $ws = $this->sheets[$worksheetIndex];
723 -
724 - if (!isset($this->hyperlinks[$worksheetIndex]) && isset($ws->hyperlinks)) {
725 - $this->hyperlinks[$worksheetIndex] = [];
726 - $sheet_rels = str_replace('worksheets', 'worksheets/_rels', $this->sheetFiles[$worksheetIndex]) . '.rels';
727 - $link_ids = [];
728 -
729 - if ($rels = $this->getEntryXML($sheet_rels)) {
730 - // hyperlink
731 -// $rel_base = dirname( $sheet_rels );
732 - foreach ($rels->Relationship as $rel) {
733 - $rel_type = basename(trim((string)$rel['Type']));
734 - if ($rel_type === 'hyperlink') {
735 - $rel_id = (string)$rel['Id'];
736 - $rel_target = (string)$rel['Target'];
737 - $link_ids[$rel_id] = $rel_target;
738 - }
739 - }
740 - }
741 - foreach ($ws->hyperlinks->hyperlink as $hyperlink) {
742 - $ref = (string)$hyperlink['ref'];
743 - if ($this->_strpos($ref, ':') > 0) { // A1:A8 -> A1
744 - $ref = explode(':', $ref);
745 - $ref = $ref[0];
746 - }
747 -// $this->hyperlinks[ $worksheetIndex ][ $ref ] = (string) $hyperlink['display'];
748 - $loc = (string)$hyperlink['location'];
749 - $id = (string)$hyperlink['id'];
750 - if ($id) {
751 - $href = $link_ids[$id] . ($loc ? '#' . $loc : '');
752 - } else {
753 - $href = $loc;
754 - }
755 - $this->hyperlinks[$worksheetIndex][$ref] = $href;
756 - }
757 - }
758 -
759 - return $ws;
710 + return $this->sheets[$worksheetIndex];
760 711 }
761 712 $this->error(6, 'Worksheet not found ' . $worksheetIndex);
762 713
763 714 return false;
@@ -762,13 +713,8 @@
762 713
763 714 return false;
764 715 }
765 716
766 - protected function _strpos($haystack, $needle, $offset = 0)
767 - {
768 - return (ini_get('mbstring.func_overload') & 2) ? mb_strpos($haystack, $needle, $offset, '8bit') : strpos($haystack, $needle, $offset);
769 - }
770 -
771 717 /**
772 718 * returns [numCols,numRows] of worksheet
773 719 *
774 720 * @param int $worksheetIndex
@@ -784,9 +730,9 @@
784 730 /* @var SimpleXMLElement $ws */
785 731
786 732 $ref = (string)$ws->dimension['ref'];
787 733
788 - if ($this->_strpos($ref, ':') !== false) {
734 + if (self::strpos($ref, ':') !== false) {
789 735 $d = explode(':', $ref);
790 736 $idx = $this->getIndex($d[1]);
791 737
792 738 return [$idx[0] + 1, $idx[1] + 1];
@@ -832,14 +778,15 @@
832 778 }
833 779
834 780 public function getIndex($cell = 'A1')
835 781 {
782 + $m = null;
836 783
837 784 if (preg_match('/([A-Z]+)(\d+)/', $cell, $m)) {
838 785 $col = $m[1];
839 786 $row = $m[2];
840 787
841 - $colLen = $this->_strlen($col);
788 + $colLen = self::strlen($col);
842 789 $index = 0;
843 790
844 791 for ($i = $colLen - 1; $i >= 0; $i--) {
845 792 $index += (ord($col[$i]) - 64) * pow(26, $colLen - $i - 1);
@@ -862,9 +809,9 @@
862 809 $s = (int)$cell['s'];
863 810 if ($s > 0 && isset($this->cellFormats[$s])) {
864 811 if (array_key_exists('format', $this->cellFormats[$s])) {
865 812 $format = $this->cellFormats[$s]['format'];
866 - if (preg_match('/(m|AM|PM)/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM
813 + if ($format && preg_match('/[mM]/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM
867 814 $dataType = 'D';
868 815 }
869 816 } else {
870 817 $dataType = 'n';
@@ -889,22 +836,15 @@
889 836 break;
890 837
891 838 case 'b':
892 839 // Value is boolean
893 - $value = (string)$cell->v;
894 - if ($value === '0') {
895 - $value = false;
896 - } elseif ($value === '1') {
897 - $value = true;
898 - } else {
899 - $value = (bool)$cell->v;
900 - }
840 + $value = self::boolean((string)$cell->v);
901 841
902 842 break;
903 843
904 844 case 'inlineStr':
905 845 // Value is rich text inline
906 - $value = $this->_parseRichText($cell->is);
846 + $value = self::parseRichText($cell->is);
907 847
908 848 break;
909 849
910 850 case 'e':
@@ -916,9 +856,10 @@
916 856
917 857 case 'D':
918 858 // Date as float
919 859 if (!empty($cell->v)) {
920 - $value = $this->datetimeFormat ? gmdate($this->datetimeFormat, $this->unixstamp((float)$cell->v)) : (float)$cell->v;
860 + $value = $this->datetimeFormat ?
861 + gmdate($this->datetimeFormat, $this->unixstamp((float)$cell->v)) : (float)$cell->v;
921 862 }
922 863 break;
923 864
924 865 case 'd':
@@ -960,14 +901,8 @@
960 901
961 902 return (int)$t;
962 903 }
963 904
964 - public function href($worksheetIndex, $cell)
965 - {
966 - $ref = (string)$cell['r'];
967 - return isset($this->hyperlinks[$worksheetIndex][$ref]) ? $this->hyperlinks[$worksheetIndex][$ref] : '';
968 - }
969 -
970 905 public function toHTML($worksheetIndex = 0)
971 906 {
972 907 $s = '<table class=excel>';
973 908 foreach ($this->readRows($worksheetIndex) as $r) {
@@ -998,9 +933,12 @@
998 933
999 934 if ($x === 0 && $c['height']) {
1000 935 $css .= 'height: '.round($c['height'] * 1.3333).'px;';
1001 936 }
1002 - $s .= '<'.$tag.' style="'.$css.'" nowrap>' . ($c['value'] === '' ? '&nbsp' : htmlspecialchars($c['value'], ENT_QUOTES)) . '</'.$tag.'>';
937 + $v = htmlspecialchars($c['value'], ENT_QUOTES);
938 + $v = preg_replace('/\R/', "<br>\r\n", $v);
939 + $s .= '<'.$tag.' style="'.$css.'" nowrap>'
940 + . ($v === '' ? '&nbsp' : $v) . '</'.$tag.'>';
1003 941 $x++;
1004 942 }
1005 943 $s .= "</tr>\r\n";
1006 944 $y++;
@@ -1082,13 +1020,13 @@
1082 1020 {
1083 1021 return iterator_to_array($this->readRowsEx($worksheetIndex, $limit), false);
1084 1022 }
1085 1023 // https://github.com/shuchkin/simplexlsx#gets-extend-cell-info-by--rowsex
1086 - /**
1024 + /**
1087 1025 * @param $worksheetIndex
1088 1026 * @param $limit
1089 1027 * @return \Generator|null
1090 - */
1028 + */
1091 1029 public function readRowsEx($worksheetIndex = 0, $limit = 0)
1092 1030 {
1093 1031 if (!$this->rowsExReader) {
1094 1032 require_once __DIR__ . '/SimpleXLSXEx.php';
@@ -1112,9 +1050,9 @@
1112 1050 if (($ws = $this->worksheet($worksheetIndex)) === false) {
1113 1051 return false;
1114 1052 }
1115 1053 if (is_array($cell)) {
1116 - $cell = $this->_num2name($cell[0]) . $cell[1];// [3,21] -> D21
1054 + $cell = self::num2name($cell[0]) . $cell[1];// [3,21] -> D21
1117 1055 }
1118 1056 if (is_string($cell)) {
1119 1057 $result = $ws->sheetData->xpath("row/c[@r='" . $cell . "']");
1120 1058 if (count($result)) {
@@ -1124,19 +1062,8 @@
1124 1062
1125 1063 return null;
1126 1064 }
1127 1065
1128 - protected function _num2name($num)
1129 - {
1130 - $numeric = ($num - 1) % 26;
1131 - $letter = chr(65 + $numeric);
1132 - $num2 = (int)(($num - 1) / 26);
1133 - if ($num2 > 0) {
1134 - return $this->_num2name($num2) . $letter;
1135 - }
1136 - return $letter;
1137 - }
1138 -
1139 1066 public function getSheets()
1140 1067 {
1141 1068 return $this->sheets;
1142 1069 }
@@ -1147,10 +1074,11 @@
1147 1074 }
1148 1075
1149 1076 public function sheetName($worksheetIndex)
1150 1077 {
1151 - if (isset($this->sheetNames[$worksheetIndex])) {
1152 - return $this->sheetNames[$worksheetIndex];
1078 + $sn = $this->sheetNames();
1079 + if (isset($sn[$worksheetIndex])) {
1080 + return $sn[$worksheetIndex];
1153 1081 }
1154 1082
1155 1083 return false;
1156 1084 }
@@ -1156,11 +1084,26 @@
1156 1084 }
1157 1085
1158 1086 public function sheetNames()
1159 1087 {
1160 -
1161 - return $this->sheetNames;
1088 + $a = [];
1089 + foreach ($this->sheetMetaData as $k => $v) {
1090 + $a[$k] = $v['name'];
1091 + }
1092 + return $a;
1162 1093 }
1094 + public function sheetMeta($worksheetIndex = null)
1095 + {
1096 + if ($worksheetIndex === null) {
1097 + return $this->sheetMetaData;
1098 + }
1099 + return isset($this->sheetMetaData[$worksheetIndex]) ? $this->sheetMetaData[$worksheetIndex] : false;
1100 + }
1101 + public function isHiddenSheet($worksheetIndex)
1102 + {
1103 + return isset($this->sheetMetaData[$worksheetIndex]['state'])
1104 + && $this->sheetMetaData[$worksheetIndex]['state'] === 'hidden';
1105 + }
1163 1106
1164 1107 public function getStyles()
1165 1108 {
1166 1109 return $this->styles;
@@ -1173,6 +1116,81 @@
1173 1116
1174 1117 public function setDateTimeFormat($value)
1175 1118 {
1176 1119 $this->datetimeFormat = is_string($value) ? $value : false;
1120 + }
1121 +
1122 + public static function getTarget($base, $target)
1123 + {
1124 + $target = trim($target);
1125 + if (strpos($target, '/') === 0) {
1126 + return self::substr($target, 1);
1127 + }
1128 + $target = ($base ? $base . '/' : '') . $target;
1129 + // a/b/../c -> a/c
1130 + $parts = explode('/', $target);
1131 + $abs = [];
1132 + foreach ($parts as $p) {
1133 + if ('.' === $p) {
1134 + continue;
1135 + }
1136 + if ('..' === $p) {
1137 + array_pop($abs);
1138 + } else {
1139 + $abs[] = $p;
1140 + }
1141 + }
1142 + return implode('/', $abs);
1143 + }
1144 +
1145 + public static function parseRichText($is = null)
1146 + {
1147 + $value = [];
1148 +
1149 + if (isset($is->t)) {
1150 + $value[] = (string)$is->t;
1151 + } elseif (isset($is->r)) {
1152 + foreach ($is->r as $run) {
1153 + $value[] = (string)$run->t;
1154 + }
1155 + }
1156 +
1157 + return implode('', $value);
1158 + }
1159 +
1160 + public static function num2name($num)
1161 + {
1162 + $numeric = ($num - 1) % 26;
1163 + $letter = chr(65 + $numeric);
1164 + $num2 = (int)(($num - 1) / 26);
1165 + if ($num2 > 0) {
1166 + return self::num2name($num2) . $letter;
1167 + }
1168 + return $letter;
1169 + }
1170 +
1171 + public static function strlen($str)
1172 + {
1173 + return (ini_get('mbstring.func_overload') & 2) ? mb_strlen($str, '8bit') : strlen($str);
1174 + }
1175 +
1176 + public static function substr($str, $start, $length = null)
1177 + {
1178 + return (ini_get('mbstring.func_overload') & 2) ?
1179 + mb_substr($str, $start, ($length === null) ? mb_strlen($str, '8bit') : $length, '8bit')
1180 + : substr($str, $start, ($length === null) ? strlen($str) : $length);
1181 + }
1182 +
1183 + public static function strpos($haystack, $needle, $offset = 0)
1184 + {
1185 + return (ini_get('mbstring.func_overload') & 2) ?
1186 + mb_strpos($haystack, $needle, $offset, '8bit') : strpos($haystack, $needle, $offset);
1187 + }
1188 + public static function boolean($value)
1189 + {
1190 + if (is_numeric($value)) {
1191 + return (bool) $value;
1192 + }
1193 +
1194 + return $value === 'true' || $value === 'TRUE';
1177 1195 }
1178 1196 }