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 +183 -184 2.3 → 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.19 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 $sheetFiles = [];
84 - protected $sheetMetaData = [];
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 ($aP['CS'] > 0 && ($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,11 +371,13 @@
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) {
@@ -384,32 +397,37 @@
384 397 if ($workbookRelations = $this->getEntryXML(dirname($rel_target) . '/_rels/workbook.xml.rels')) {
385 398 // Loop relations for workbook and extract sheets...
386 399 foreach ($workbookRelations->Relationship as $workbookRelation) {
387 400 $wrel_type = basename(trim((string)$workbookRelation['Type'])); // worksheet
388 - $wrel_path = $this->_getTarget(dirname($rel_target), (string)$workbookRelation['Target']);
389 - if (!$this->entryExists($wrel_path)) {
401 + $wrel_target = self::getTarget(dirname($rel_target), (string)$workbookRelation['Target']);
402 + if (!$this->entryExists($wrel_target)) {
390 403 continue;
391 404 }
392 405
393 -
394 406 if ($wrel_type === 'worksheet') { // Sheets
395 - if ($sheet = $this->getEntryXML($wrel_path)) {
407 + if ($sheet = $this->getEntryXML($wrel_target)) {
396 408 $index = array_search((string)$workbookRelation['Id'], $index_rId, true);
397 409 $this->sheets[$index] = $sheet;
398 - $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 + }
399 417 }
400 418 } elseif ($wrel_type === 'sharedStrings') {
401 - if ($sharedStrings = $this->getEntryXML($wrel_path)) {
419 + if ($sharedStrings = $this->getEntryXML($wrel_target)) {
402 420 foreach ($sharedStrings->si as $val) {
403 421 if (isset($val->t)) {
404 422 $this->sharedstrings[] = (string)$val->t;
405 423 } elseif (isset($val->r)) {
406 - $this->sharedstrings[] = $this->_parseRichText($val);
424 + $this->sharedstrings[] = self::parseRichText($val);
407 425 }
408 426 }
409 427 }
410 428 } elseif ($wrel_type === 'styles') {
411 - $this->styles = $this->getEntryXML($wrel_path);
429 + $this->styles = $this->getEntryXML($wrel_target);
412 430
413 431 // number formats
414 432 $this->nf = [];
415 433 if (isset($this->styles->numFmts->numFmt)) {
@@ -438,20 +456,24 @@
438 456 $this->cellFormats[] = $x;
439 457 }
440 458 }
441 459 } elseif ($wrel_type === 'theme') {
442 - $this->theme = $this->getEntryXML($wrel_path);
460 + $this->theme = $this->getEntryXML($wrel_target);
443 461 }
444 462 }
445 463
446 - break;
464 +// break;
447 465 }
448 466 // reptile hack :: find active sheet from workbook.xml
449 - foreach ($workbook->bookViews->workbookView as $s) {
450 - if (!empty($s['activeTab'])) {
451 - $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 + }
452 472 }
453 473 }
474 +
475 + break;
454 476 }
455 477 }
456 478 }
457 479
@@ -474,9 +496,10 @@
474 496 $this->deleteEntry($name); // economy memory
475 497 // dirty remove namespace prefixes and empty rows
476 498 $entry_xml = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $entry_xml); // remove namespaces
477 499 $entry_xml .= ' '; // force run garbage collector
478 - $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);
479 502 $entry_xml .= ' ';
480 503 $entry_xml = preg_replace('/<[a-zA-Z0-9]+:([^>]+)>/', '<$1>', $entry_xml); // fix namespaced openned tags
481 504 $entry_xml .= ' ';
482 505 $entry_xml = preg_replace('/<\/[a-zA-Z0-9]+:([^>]+)>/', '</$1>', $entry_xml); // fix namespaced closed tags
@@ -483,8 +506,9 @@
483 506 $entry_xml .= ' ';
484 507
485 508 if (strpos($name, '/sheet')) { // dirty skip empty rows
486 509 // remove <row...> <c /><c /></row>
510 + $cnt = $cnt2 = $cnt3 = null;
487 511 $entry_xml = preg_replace('/<row[^>]+>\s*(<c[^\/]+\/>\s*)+<\/row>/', '', $entry_xml, -1, $cnt);
488 512 $entry_xml .= ' ';
489 513 // remove <row />
490 514 $entry_xml = preg_replace('/<row[^\/>]*\/>/', '', $entry_xml, -1, $cnt2);
@@ -538,12 +562,12 @@
538 562
539 563 public function getEntryData($name)
540 564 {
541 565 $name = ltrim(str_replace('\\', '/', $name), '/');
542 - $dir = $this->_strtoupper(dirname($name));
543 - $name = $this->_strtoupper(basename($name));
566 + $dir = self::strtoupper(dirname($name));
567 + $name = self::strtoupper(basename($name));
544 568 foreach ($this->package['entries'] as &$entry) {
545 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
569 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
546 570 if ($entry['error']) {
547 571 return false;
548 572 }
549 573 switch ($entry['cm']) {
@@ -570,9 +594,9 @@
570 594 $entry['cm'] = -1;
571 595 if ($entry['data'] === false) {
572 596 $entry['error'] = 2;
573 597 $entry['error_msg'] = 'Decompression of data failed.';
574 - } elseif ($entry['ucs'] > 0 && ($this->_strlen($entry['data']) !== (int)$entry['ucs'])) {
598 + } elseif ($entry['ucs'] > 0 && (self::strlen($entry['data']) !== (int)$entry['ucs'])) {
575 599 $entry['error'] = 3;
576 600 $entry['error_msg'] = 'Uncompressed size is not equal with the value in header information.';
577 601 } elseif (crc32($entry['data']) !== $entry['crc']) {
578 602 $entry['error'] = 4;
@@ -590,12 +614,12 @@
590 614 }
591 615 public function deleteEntry($name)
592 616 {
593 617 $name = ltrim(str_replace('\\', '/', $name), '/');
594 - $dir = $this->_strtoupper(dirname($name));
595 - $name = $this->_strtoupper(basename($name));
618 + $dir = self::strtoupper(dirname($name));
619 + $name = self::strtoupper(basename($name));
596 620 foreach ($this->package['entries'] as $k => $entry) {
597 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
621 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
598 622 unset($this->package['entries'][$k]);
599 623 return true;
600 624 }
601 625 }
@@ -601,36 +625,13 @@
601 625 }
602 626 return false;
603 627 }
604 628
605 - protected function _strtoupper($str)
629 + public static function strtoupper($str)
606 630 {
607 631 return (ini_get('mbstring.func_overload') & 2) ? mb_strtoupper($str, '8bit') : strtoupper($str);
608 632 }
609 633
610 - protected function _getTarget($base, $target)
611 - {
612 - $target = trim($target);
613 - if (strpos($target, '/') === 0) {
614 - return $this->_substr($target, 1);
615 - }
616 - $target = ($base ? $base . '/' : '') . $target;
617 - // a/b/../c -> a/c
618 - $parts = explode('/', $target);
619 - $abs = [];
620 - foreach ($parts as $p) {
621 - if ('.' === $p) {
622 - continue;
623 - }
624 - if ('..' === $p) {
625 - array_pop($abs);
626 - } else {
627 - $abs[] = $p;
628 - }
629 - }
630 - return implode('/', $abs);
631 - }
632 -
633 634 /*
634 635 * @param string $name Filename in archive
635 636 * @return SimpleXMLElement|bool
636 637 */
@@ -637,12 +638,12 @@
637 638
638 639 public function entryExists($name)
639 640 {
640 641 // 0.6.6
641 - $dir = $this->_strtoupper(dirname($name));
642 - $name = $this->_strtoupper(basename($name));
642 + $dir = self::strtoupper(dirname($name));
643 + $name = self::strtoupper(basename($name));
643 644 foreach ($this->package['entries'] as $entry) {
644 - if ($this->_strtoupper($entry['path']) === $dir && $this->_strtoupper($entry['name']) === $name) {
645 + if (self::strtoupper($entry['path']) === $dir && self::strtoupper($entry['name']) === $name) {
645 646 return true;
646 647 }
647 648 }
648 649
@@ -648,23 +649,8 @@
648 649
649 650 return false;
650 651 }
651 652
652 - protected function _parseRichText($is = null)
653 - {
654 - $value = [];
655 -
656 - if (isset($is->t)) {
657 - $value[] = (string)$is->t;
658 - } elseif (isset($is->r)) {
659 - foreach ($is->r as $run) {
660 - $value[] = (string)$run->t;
661 - }
662 - }
663 -
664 - return implode('', $value);
665 - }
666 -
667 653 public static function parseFile($filename, $debug = false)
668 654 {
669 655 return self::parse($filename, false, $debug);
670 656 }
@@ -672,10 +658,10 @@
672 658 public static function parse($filename, $is_data = false, $debug = false)
673 659 {
674 660 $xlsx = new self();
675 661 $xlsx->debug = $debug;
676 - if ($xlsx->_unzip($filename, $is_data)) {
677 - $xlsx->_parse();
662 + if ($xlsx->unzip($filename, $is_data)) {
663 + $xlsx->parseEntries();
678 664 }
679 665 if ($xlsx->success()) {
680 666 return $xlsx;
681 667 }
@@ -719,49 +705,10 @@
719 705
720 706
721 707 public function worksheet($worksheetIndex = 0)
722 708 {
723 -
724 -
725 709 if (isset($this->sheets[$worksheetIndex])) {
726 - $ws = $this->sheets[$worksheetIndex];
727 -
728 - if (!isset($this->hyperlinks[$worksheetIndex]) && isset($ws->hyperlinks)) {
729 - $this->hyperlinks[$worksheetIndex] = [];
730 - $sheet_rels = str_replace('worksheets', 'worksheets/_rels', $this->sheetFiles[$worksheetIndex]) . '.rels';
731 - $link_ids = [];
732 -
733 - if ($rels = $this->getEntryXML($sheet_rels)) {
734 - // hyperlink
735 -// $rel_base = dirname( $sheet_rels );
736 - foreach ($rels->Relationship as $rel) {
737 - $rel_type = basename(trim((string)$rel['Type']));
738 - if ($rel_type === 'hyperlink') {
739 - $rel_id = (string)$rel['Id'];
740 - $rel_target = (string)$rel['Target'];
741 - $link_ids[$rel_id] = $rel_target;
742 - }
743 - }
744 - }
745 - foreach ($ws->hyperlinks->hyperlink as $hyperlink) {
746 - $ref = (string)$hyperlink['ref'];
747 - if ($this->_strpos($ref, ':') > 0) { // A1:A8 -> A1
748 - $ref = explode(':', $ref);
749 - $ref = $ref[0];
750 - }
751 -// $this->hyperlinks[ $worksheetIndex ][ $ref ] = (string) $hyperlink['display'];
752 - $loc = (string)$hyperlink['location'];
753 - $id = (string)$hyperlink['id'];
754 - if ($id) {
755 - $href = $link_ids[$id] . ($loc ? '#' . $loc : '');
756 - } else {
757 - $href = $loc;
758 - }
759 - $this->hyperlinks[$worksheetIndex][$ref] = $href;
760 - }
761 - }
762 -
763 - return $ws;
710 + return $this->sheets[$worksheetIndex];
764 711 }
765 712 $this->error(6, 'Worksheet not found ' . $worksheetIndex);
766 713
767 714 return false;
@@ -766,13 +713,8 @@
766 713
767 714 return false;
768 715 }
769 716
770 - protected function _strpos($haystack, $needle, $offset = 0)
771 - {
772 - return (ini_get('mbstring.func_overload') & 2) ? mb_strpos($haystack, $needle, $offset, '8bit') : strpos($haystack, $needle, $offset);
773 - }
774 -
775 717 /**
776 718 * returns [numCols,numRows] of worksheet
777 719 *
778 720 * @param int $worksheetIndex
@@ -788,9 +730,9 @@
788 730 /* @var SimpleXMLElement $ws */
789 731
790 732 $ref = (string)$ws->dimension['ref'];
791 733
792 - if ($this->_strpos($ref, ':') !== false) {
734 + if (self::strpos($ref, ':') !== false) {
793 735 $d = explode(':', $ref);
794 736 $idx = $this->getIndex($d[1]);
795 737
796 738 return [$idx[0] + 1, $idx[1] + 1];
@@ -836,14 +778,15 @@
836 778 }
837 779
838 780 public function getIndex($cell = 'A1')
839 781 {
782 + $m = null;
840 783
841 784 if (preg_match('/([A-Z]+)(\d+)/', $cell, $m)) {
842 785 $col = $m[1];
843 786 $row = $m[2];
844 787
845 - $colLen = $this->_strlen($col);
788 + $colLen = self::strlen($col);
846 789 $index = 0;
847 790
848 791 for ($i = $colLen - 1; $i >= 0; $i--) {
849 792 $index += (ord($col[$i]) - 64) * pow(26, $colLen - $i - 1);
@@ -866,9 +809,9 @@
866 809 $s = (int)$cell['s'];
867 810 if ($s > 0 && isset($this->cellFormats[$s])) {
868 811 if (array_key_exists('format', $this->cellFormats[$s])) {
869 812 $format = $this->cellFormats[$s]['format'];
870 - if (preg_match('/[mM]/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM
813 + if ($format && preg_match('/[mM]/', preg_replace('/\"[^"]+\"/', '', $format))) { // [mm]onth,AM|PM
871 814 $dataType = 'D';
872 815 }
873 816 } else {
874 817 $dataType = 'n';
@@ -893,22 +836,15 @@
893 836 break;
894 837
895 838 case 'b':
896 839 // Value is boolean
897 - $value = (string)$cell->v;
898 - if ($value === '0') {
899 - $value = false;
900 - } elseif ($value === '1') {
901 - $value = true;
902 - } else {
903 - $value = (bool)$cell->v;
904 - }
840 + $value = self::boolean((string)$cell->v);
905 841
906 842 break;
907 843
908 844 case 'inlineStr':
909 845 // Value is rich text inline
910 - $value = $this->_parseRichText($cell->is);
846 + $value = self::parseRichText($cell->is);
911 847
912 848 break;
913 849
914 850 case 'e':
@@ -920,9 +856,10 @@
920 856
921 857 case 'D':
922 858 // Date as float
923 859 if (!empty($cell->v)) {
924 - $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;
925 862 }
926 863 break;
927 864
928 865 case 'd':
@@ -964,14 +901,8 @@
964 901
965 902 return (int)$t;
966 903 }
967 904
968 - public function href($worksheetIndex, $cell)
969 - {
970 - $ref = (string)$cell['r'];
971 - return isset($this->hyperlinks[$worksheetIndex][$ref]) ? $this->hyperlinks[$worksheetIndex][$ref] : '';
972 - }
973 -
974 905 public function toHTML($worksheetIndex = 0)
975 906 {
976 907 $s = '<table class=excel>';
977 908 foreach ($this->readRows($worksheetIndex) as $r) {
@@ -1002,9 +933,12 @@
1002 933
1003 934 if ($x === 0 && $c['height']) {
1004 935 $css .= 'height: '.round($c['height'] * 1.3333).'px;';
1005 936 }
1006 - $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.'>';
1007 941 $x++;
1008 942 }
1009 943 $s .= "</tr>\r\n";
1010 944 $y++;
@@ -1116,9 +1050,9 @@
1116 1050 if (($ws = $this->worksheet($worksheetIndex)) === false) {
1117 1051 return false;
1118 1052 }
1119 1053 if (is_array($cell)) {
1120 - $cell = $this->_num2name($cell[0]) . $cell[1];// [3,21] -> D21
1054 + $cell = self::num2name($cell[0]) . $cell[1];// [3,21] -> D21
1121 1055 }
1122 1056 if (is_string($cell)) {
1123 1057 $result = $ws->sheetData->xpath("row/c[@r='" . $cell . "']");
1124 1058 if (count($result)) {
@@ -1128,19 +1062,8 @@
1128 1062
1129 1063 return null;
1130 1064 }
1131 1065
1132 - protected function _num2name($num)
1133 - {
1134 - $numeric = ($num - 1) % 26;
1135 - $letter = chr(65 + $numeric);
1136 - $num2 = (int)(($num - 1) / 26);
1137 - if ($num2 > 0) {
1138 - return $this->_num2name($num2) . $letter;
1139 - }
1140 - return $letter;
1141 - }
1142 -
1143 1066 public function getSheets()
1144 1067 {
1145 1068 return $this->sheets;
1146 1069 }
@@ -1176,9 +1099,10 @@
1176 1099 return isset($this->sheetMetaData[$worksheetIndex]) ? $this->sheetMetaData[$worksheetIndex] : false;
1177 1100 }
1178 1101 public function isHiddenSheet($worksheetIndex)
1179 1102 {
1180 - return isset($this->sheetMetaData[$worksheetIndex]['state']) && $this->sheetMetaData[$worksheetIndex]['state'] === 'hidden';
1103 + return isset($this->sheetMetaData[$worksheetIndex]['state'])
1104 + && $this->sheetMetaData[$worksheetIndex]['state'] === 'hidden';
1181 1105 }
1182 1106
1183 1107 public function getStyles()
1184 1108 {
@@ -1192,6 +1116,81 @@
1192 1116
1193 1117 public function setDateTimeFormat($value)
1194 1118 {
1195 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';
1196 1195 }
1197 1196 }