PluginProbe
TablePress – Tables in WordPress made easy / 1.9.2
TablePress – Tables in WordPress made easy v1.9.2
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 2.4.4 All 44 releases
tablepress / libraries / simplexlsx.class.php

simplexlsx.class.php in TablePress – Tables in WordPress made easy 1.9.2, at libraries/simplexlsx.class.php

986 lines 23.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Excel 2007-2013 Reader Class
4 *
5 * Based on SimpleXLSX v0.7.13 by Sergey Schuchkin.
6 * @link https://github.com/shuchkin/simplexlsx/
7 *
8 * @package TablePress
9 * @subpackage Import
10 * @author Sergey Schuchkin, Tobias Bäthge
11 * @since 1.1.0
12 */
13
14 // Prohibit direct script loading.
15 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
16
17 /**
18 * PHP Excel 2007-2013 Reader Class
19 * @package TablePress
20 * @subpackage Import
21 * @author Sergey Schuchkin, Tobias Bäthge
22 * @since 1.1.0
23 */
24 class SimpleXLSX {
25
26 /**
27 * XML Schema URLs.
28 */
29 const SCHEMA_REL_OFFICEDOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
30 const SCHEMA_REL_SHAREDSTRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
31 const SCHEMA_REL_WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
32 const SCHEMA_REL_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
33
34 /**
35 * [$workbook description]
36 *
37 * @since 1.1.0
38 * @var [type]
39 */
40 protected $workbook;
41
42 /**
43 * [$sheets description]
44 *
45 * @since 1.1.0
46 * @var array
47 */
48 protected $sheets = array();
49
50 /**
51 * [$sheetNames description]
52 *
53 * @since 1.9.1
54 * @var array
55 */
56 protected $sheetNames = array();
57
58 /**
59 * [$styles description]
60 *
61 * @since 1.1.0
62 * @var array
63 */
64 protected $styles = array();
65
66 /**
67 * [$hyperlinks description]
68 *
69 * @since 1.1.0
70 * @var [type]
71 */
72 protected $hyperlinks;
73
74 /**
75 * [$package description]
76 *
77 * @since 1.1.0
78 * @var array
79 */
80 protected $package = array(
81 'filename' => '',
82 'mtime' => 0,
83 'size' => 0,
84 'comment' => '',
85 'entries' => array(),
86 );
87
88 /**
89 * [$sharedstrings description]
90 *
91 * @since 1.1.0
92 * @var array
93 */
94 protected $sharedstrings = array();
95
96 /**
97 * [$error description]
98 *
99 * @since 1.1.0
100 * @var string
101 */
102 protected $error = '';
103
104 /**
105 * [$workbook_cell_formats description]
106 *
107 * @since 1.1.0
108 * @var array
109 */
110 public $workbook_cell_formats = array();
111
112 /**
113 * [$built_in_cell_formats description]
114 *
115 * @since 1.1.0
116 * @var array
117 */
118 public static $built_in_cell_formats = array(
119 0 => 'General',
120 1 => '0',
121 2 => '0.00',
122 3 => '#,##0',
123 4 => '#,##0.00',
124 9 => '0%',
125 10 => '0.00%',
126 11 => '0.00E+00',
127 12 => '# ?/?',
128 13 => '# ??/??',
129 14 => 'mm-dd-yy',
130 15 => 'd-mmm-yy',
131 16 => 'd-mmm',
132 17 => 'mmm-yy',
133 18 => 'h:mm AM/PM',
134 19 => 'h:mm:ss AM/PM',
135 20 => 'h:mm',
136 21 => 'h:mm:ss',
137 22 => 'm/d/yy h:mm',
138 37 => '#,##0 ;(#,##0)',
139 38 => '#,##0 ;[Red](#,##0)',
140 39 => '#,##0.00;(#,##0.00)',
141 40 => '#,##0.00;[Red](#,##0.00)',
142 44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
143 45 => 'mm:ss',
144 46 => '[h]:mm:ss',
145 47 => 'mmss.0',
146 48 => '##0.0E+0',
147 49 => '@',
148 27 => '[$-404]e/m/d',
149 30 => 'm/d/yy',
150 36 => '[$-404]e/m/d',
151 50 => '[$-404]e/m/d',
152 57 => '[$-404]e/m/d',
153 59 => 't0',
154 60 => 't0.00',
155 61 => 't#,##0',
156 62 => 't#,##0.00',
157 67 => 't0%',
158 68 => 't0.00%',
159 69 => 't# ?/?',
160 70 => 't# ??/??',
161 );
162
163 /**
164 * [$datetime_format description]
165 *
166 * @since 1.8.1
167 * @var string
168 */
169 public $datetime_format = 'Y-m-d H:i:s';
170
171 /**
172 * Constructor.
173 *
174 * @since 1.1.0
175 *
176 * @param string $filename [description]
177 * @param bool $is_data Optional. [description]
178 */
179 public function __construct( $filename, $is_data = false ) {
180 $this->datetime_format = get_option( 'date_format' );
181 if ( $this->_unzip( $filename, $is_data ) ) {
182 $this->_parse();
183 }
184 }
185
186 /**
187 * [sheets description]
188 *
189 * @since 1.1.0
190 *
191 * @return [type] [description]
192 */
193 public function sheets() {
194 return $this->sheets;
195 }
196
197 /**
198 * [sheetsCount description]
199 *
200 * @since 1.1.0
201 *
202 * @return int Number of sheets.
203 */
204 public function sheetsCount() {
205 return count( $this->sheets );
206 }
207
208 /**
209 * [sheetName description]
210 *
211 * @since 1.1.0
212 *
213 * @param [type] $worksheet_index [description]
214 * @return string|bool [description]
215 */
216 public function sheetName( $worksheet_index ) {
217 if ( isset( $this->sheetNames[ $worksheet_index ] ) ) {
218 return $this->sheetNames[ $worksheet_index ];
219 }
220 return false;
221 }
222
223 /**
224 * [sheetNames description]
225 *
226 * @since 1.1.0
227 *
228 * @return array [description]
229 */
230 public function sheetNames() {
231 return $this->sheetNames;
232 }
233
234 /**
235 * [worksheet description]
236 *
237 * @since 1.1.0
238 *
239 * @param [type] $worksheet_index [description]. Optional.
240 * @return [type] [description]
241 */
242 public function worksheet( $worksheet_index = 0 ) {
243 if ( isset( $this->sheets[ $worksheet_index ] ) ) {
244 $ws = $this->sheets[ $worksheet_index ];
245 if ( isset( $ws->hyperlinks ) ) {
246 $this->hyperlinks = array();
247 foreach ( $ws->hyperlinks->hyperlink as $hyperlink ) {
248 $this->hyperlinks[ (string) $hyperlink['ref'] ] = (string) $hyperlink['display'];
249 }
250 }
251 return $ws;
252 }
253 $this->error( 'Worksheet ' . $worksheet_index . ' not found.' );
254 return false;
255 }
256
257 /**
258 * [dimension description]
259 *
260 * "Don't trust ->dimension(), so xlsx generators very lazy and don't publish a dimension attribute."
261 *
262 * @since 1.1.0
263 *
264 * @param int $worksheet_index Optional. [description]
265 * @return array|false [description]
266 */
267 public function dimension( $worksheet_index = 0 ) {
268 if ( false === ( $ws = $this->worksheet( $worksheet_index ) ) ) {
269 return false;
270 }
271
272 $ref = (string) $ws->dimension['ref'];
273
274 if ( false !== strpos( $ref, ':' ) ) {
275 $d = explode( ':', $ref );
276 $index = $this->_columnIndex( $d[1] );
277 return array( $index[0] + 1, $index[1] + 1 );
278 }
279
280 if ( '' !== $ref ) {
281 $index = $this->_columnIndex( $ref );
282 return array( $index[0] + 1, $index[1] + 1 );
283 }
284
285 return array( 0, 0 );
286 }
287
288 /**
289 * [rows description]
290 *
291 * Sheets numeration: 1, 2, 3, ...
292 *
293 * @since 1.1.0
294 *
295 * @param int $worksheet_index Optional. [description]
296 * @return array|bool [description]
297 */
298 public function rows( $worksheet_index = 0 ) {
299 if ( false === ( $ws = $this->worksheet( $worksheet_index ) ) ) {
300 return false;
301 }
302
303 $rows = array();
304 $current_row = 0;
305
306 list( $cols, ) = $this->dimension( $worksheet_index );
307
308 foreach ( $ws->sheetData->row as $row ) {
309 $rows[ $current_row ] = array();
310 foreach ( $row->c as $c ) {
311 list( $current_cell, ) = $this->_columnIndex( (string) $c['r'] );
312 $rows[ $current_row ][ $current_cell ] = $this->value( $c );
313 }
314 for ( $i = 0; $i < $cols; $i++ ) {
315 if ( ! isset( $rows[ $current_row ][ $i ] ) ) {
316 $rows[ $current_row ][ $i ] = '';
317 }
318 }
319 ksort( $rows[ $current_row ] );
320 $current_row++;
321 }
322 return $rows;
323 }
324
325 /**
326 * [rowsEx description]
327 *
328 * @since 1.1.0
329 *
330 * @param int $worksheet_index Optional. [description]
331 * @return array|bool [description]
332 */
333 public function rowsEx( $worksheet_index = 0 ) {
334 if ( false === ( $ws = $this->worksheet( $worksheet_index ) ) ) {
335 return false;
336 }
337
338 $rows = array();
339 $current_row = 0;
340 list( $cols, ) = $this->dimension( $worksheet_index );
341
342 foreach ( $ws->sheetData->row as $row ) {
343 $r_idx = (int) $row['r'];
344 foreach ( $row->c as $c ) {
345 $r = (string) $c['r'];
346 $t = (string) $c['t'];
347 $s = (int) $c['s'];
348 list( $current_cell, ) = $this->_columnIndex( $r );
349 if ( $s > 0 && isset( $this->workbook_cell_formats[ $s ] ) ) {
350 $format = $this->workbook_cell_formats[ $s ]['format'];
351 if ( false !== strpos( $format, 'm' ) ) {
352 $t = 'd';
353 }
354 } else {
355 $format = '';
356 }
357
358 $rows[ $current_row ][ $current_cell ] = array(
359 'type' => $t,
360 'name' => $r,
361 'value' => $this->value( $c, $format ),
362 'href' => $this->href( $c ),
363 'f' => (string) $c->f,
364 'format' => $format,
365 'r' => $r_idx,
366 );
367 }
368 for ( $i = 0; $i < $cols; $i++ ) {
369 if ( ! isset( $rows[ $current_row ][ $i ] ) ) {
370 for ( $c = '', $j = $i; $j >= 0; $j = (int) ( $j / 26 ) - 1 ) {
371 $c = chr( $j % 26 + 65 ) . $c;
372 }
373
374 $rows[ $current_row ][ $i ] = array(
375 'type' => '',
376 // 'name' => chr( $i + 65 ) . ( $current_row + 1 ),
377 'name' => $c . ( $current_row + 1 ),
378 'value' => '',
379 'href' => '',
380 'f' => '',
381 'format' => '',
382 'r' => $r_idx,
383 );
384 }
385 }
386 ksort( $rows[ $current_row ] );
387 $current_row++;
388 }
389 return $rows;
390 }
391
392 /**
393 * [_columnIndex description]
394 *
395 * @since 1.1.0
396 *
397 * @param string $cell Optional. [description]
398 * @return array [description]
399 */
400 protected function _columnIndex( $cell = 'A1' ) {
401 if ( preg_match( '/([A-Z]+)(\d+)/', $cell, $m ) ) {
402 list( , $col, $row ) = $m;
403
404 $colLen = strlen( $col );
405 $index = 0;
406
407 for ( $i = $colLen - 1; $i >= 0; $i-- ) {
408 $index += ( ord( $col[ $i ] ) - 64 ) * pow( 26, $colLen - $i - 1 );
409 }
410
411 return array( $index - 1, $row - 1 );
412 }
413 $this->error( 'Invalid cell index ' . $cell );
414
415 return false;
416 }
417
418 /**
419 * [value description]
420 *
421 * @since 1.1.0
422 *
423 * @param [type] $cell [description]
424 * @param [type] $format [description]
425 * @return mixed [description]
426 */
427 public function value( $cell, $format = null ) {
428 // Determine data type.
429 $dataType = (string) $cell['t'];
430
431 if ( null === $format ) {
432 $s = (int) $cell['s'];
433 if ( $s > 0 && isset( $this->workbook_cell_formats[ $s ] ) ) {
434 $format = $this->workbook_cell_formats[ $s ]['format'];
435 }
436 }
437 if ( false !== strpos( $format, 'm' ) ) {
438 $dataType = 'd';
439 }
440 $value = '';
441 switch ( $dataType ) {
442 case 's':
443 // Value is a shared string.
444 if ( '' !== (string) $cell->v ) {
445 $value = $this->sharedstrings[ (int) $cell->v ];
446 }
447 break;
448 case 'b':
449 // Value is boolean.
450 $value = (string) $cell->v;
451 if ( '0' === $value ) {
452 $value = false;
453 } elseif ( '1' === $value ) {
454 $value = true;
455 } else {
456 $value = (bool) $cell->v;
457 }
458 break;
459 case 'inlineStr':
460 // Value is rich text inline.
461 $value = $this->_parseRichText( $cell->is );
462 break;
463 case 'e':
464 // Value is an error message.
465 if ( '' !== (string) $cell->v ) {
466 $value = (string) $cell->v;
467 }
468 break;
469 case 'd':
470 // Value is a date.
471 $value = $this->datetime_format ? gmdate( $this->datetime_format, $this->unixstamp( (float) $cell->v ) ) : (float) $cell->v;
472 break;
473 default:
474 // Value is a string.
475 $value = (string) $cell->v;
476 // Check for numeric values by converting them forth and back.
477 if ( is_numeric( $value ) && 's' !== $dataType ) {
478 if ( $value == (int) $value ) {
479 $value = (int) $value;
480 } elseif ( $value == (float) $value ) {
481 $value = (float) $value;
482 }
483 }
484 }
485 return $value;
486 }
487
488 /**
489 * [href description]
490 *
491 * @since 1.1.0
492 *
493 * @param [type] $cell [description]
494 * @return string [description]
495 */
496 public function href( $cell ) {
497 return isset( $this->hyperlinks[ (string) $cell['r'] ] ) ? $this->hyperlinks[ (string) $cell['r'] ] : '';
498 }
499
500 /**
501 * [getStyles description]
502 *
503 * @since 1.8.1
504 *
505 * @return [type] [description]
506 */
507 public function getStyles() {
508 return $this->styles;
509 }
510
511 /**
512 * [_unzip description]
513 *
514 * @since 1.1.0
515 *
516 * @param [type] $filename [description]
517 * @param bool $is_data Optional. [description]
518 * @return [type] [description]
519 */
520 protected function _unzip( $filename, $is_data = false ) {
521 if ( $is_data ) {
522 $this->package['filename'] = 'default.xlsx';
523 $this->package['mtime'] = time();
524 $this->package['size'] = strlen( $filename );
525 $vZ = $filename;
526 } else {
527 if ( ! is_readable( $filename ) ) {
528 $this->error( 'File not found ' . $filename );
529 return false;
530 }
531
532 // Package information.
533 $this->package['filename'] = $filename;
534 $this->package['mtime'] = filemtime( $filename );
535 $this->package['size'] = filesize( $filename );
536
537 // Read file.
538 $vZ = file_get_contents( $filename );
539 }
540
541 /*
542 // Cut end of central directory
543 $aE = explode( "\x50\x4b\x05\x06", $vZ );
544 if ( 1 === count( $aE ) ) {
545 $this->error( 'Unknown format' );
546 return false;
547 }
548 */
549
550 if ( false === ( $pcd = strrpos( $vZ, "\x50\x4b\x05\x06" ) ) ) {
551 $this->error( 'Unknown archive format' );
552 return false;
553 }
554 $aE = array(
555 0 => substr( $vZ, 0, $pcd ),
556 1 => substr( $vZ, $pcd + 3 ),
557 );
558
559 // Normal way.
560 $aP = unpack( 'x16/v1CL', $aE[1] );
561 $this->package['comment'] = substr( $aE[1], 18, $aP['CL'] );
562
563 // Translates end of line from other operating systems.
564 $this->package['comment'] = str_replace( array( "\r\n", "\r" ), "\n", $this->package['comment'] );
565
566 // Cut the entries from the central directory.
567 $aE = explode( "\x50\x4b\x01\x02", $vZ );
568 // Explode to each part.
569 $aE = explode( "\x50\x4b\x03\x04", $aE[0] );
570 // Shift out spanning signature or empty entry.
571 array_shift( $aE );
572
573 // Loop through the entries.
574 foreach ( $aE as $vZ ) {
575 $aI = array();
576 $aI['E'] = 0;
577 $aI['EM'] = '';
578 // Retrieving local file header information.
579 // $aP = unpack( 'v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ );
580 $aP = unpack( 'v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ );
581
582 // Check if data is encrypted.
583 // $bE = ( $aP['GPF'] && 0x0001 ) ? true : false;
584 $bE = false;
585 $nF = $aP['FNL'];
586 $mF = $aP['EFL'];
587
588 // Special case: value block after the compressed data.
589 if ( $aP['GPF'] & 0x0008 ) {
590 $aP1 = unpack( 'V1CRC/V1CS/V1UCS', substr( $vZ, -12 ) );
591 $aP['CRC'] = $aP1['CRC'];
592 $aP['CS'] = $aP1['CS'];
593 $aP['UCS'] = $aP1['UCS'];
594 // 2013-08-10
595 $vZ = substr( $vZ, 0, -12 );
596 if ( "\x50\x4b\x07\x08" === substr( $vZ, -4 ) ) {
597 $vZ = substr( $vZ, 0, -4 );
598 }
599 }
600
601 // Get stored filename.
602 $aI['N'] = substr( $vZ, 26, $nF );
603
604 // If it's a directory entry, it will be skipped.
605 if ( '/' === substr( $aI['N'], -1 ) ) {
606 continue;
607 }
608
609 // Truncate full filename in path and filename.
610 $aI['P'] = dirname( $aI['N'] );
611 $aI['P'] = ( '.' === $aI['P'] ) ? '' : $aI['P'];
612 $aI['N'] = basename( $aI['N'] );
613
614 $vZ = substr( $vZ, 26 + $nF + $mF );
615
616 if ( strlen( $vZ ) !== (int) $aP['CS'] ) { // Check only if available.
617 $aI['E'] = 1;
618 $aI['EM'] = 'Compressed size is not equal with the value in header information.';
619 } elseif ( $bE ) {
620 $aI['E'] = 5;
621 $aI['EM'] = 'File is encrypted, which is not supported by this class.';
622 } else {
623 switch ( $aP['CM'] ) {
624 case 0: // Stored
625 // Here is nothing to do, the file is flat.
626 break;
627 case 8: // Deflated
628 $vZ = gzinflate( $vZ );
629 break;
630 case 12: // BZIP2
631 if ( extension_loaded( 'bz2' ) ) {
632 $vZ = bzdecompress( $vZ );
633 } else {
634 $aI['E'] = 7;
635 $aI['EM'] = 'PHP BZIP2 extension not available.';
636 }
637 break;
638 default:
639 $aI['E'] = 6;
640 $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
641 }
642 if ( ! $aI['E'] ) {
643 if ( false === $vZ ) {
644 $aI['E'] = 2;
645 $aI['EM'] = 'Decompression of data failed.';
646 } elseif ( strlen( $vZ ) !== (int) $aP['UCS'] ) {
647 $aI['E'] = 3;
648 $aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
649 } elseif ( crc32( $vZ ) !== $aP['CRC'] ) {
650 $aI['E'] = 4;
651 $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
652 }
653 }
654 }
655
656 $aI['D'] = $vZ;
657
658 // DOS to UNIX timestamp.
659 $aI['T'] = mktime(
660 ( $aP['FT'] & 0xf800 ) >> 11,
661 ( $aP['FT'] & 0x07e0 ) >> 5,
662 ( $aP['FT'] & 0x001f ) << 1,
663 ( $aP['FD'] & 0x01e0 ) >> 5,
664 ( $aP['FD'] & 0x001f ),
665 ( ( $aP['FD'] & 0xfe00 ) >> 9 ) + 1980
666 );
667
668 // $this->Entries[] = new SimpleUnzipEntry( $aI );
669 $this->package['entries'][] = array(
670 'data' => $aI['D'],
671 'error' => $aI['E'],
672 'error_msg' => $aI['EM'],
673 'name' => $aI['N'],
674 'path' => $aI['P'],
675 'time' => $aI['T'],
676 );
677
678 } // end foreach entries
679
680 return true;
681 }
682
683 /**
684 * [getPackage description]
685 *
686 * @since 1.1.0
687 *
688 * @return [type] [description]
689 */
690 public function getPackage() {
691 return $this->package;
692 }
693
694 /**
695 * [entryExists description]
696 *
697 * @since 1.1.0
698 *
699 * @param [type] $name [description]
700 * @return bool [description]
701 */
702 public function entryExists( $name ) {
703 $dir = strtoupper( dirname( $name ) );
704 $name = strtoupper( basename( $name ) );
705 foreach ( $this->package['entries'] as $entry ) {
706 if ( strtoupper( $entry['path'] ) === $dir && strtoupper( $entry['name'] ) === $name ) {
707 return true;
708 }
709 }
710 return false;
711 }
712
713 /**
714 * [getEntryData description]
715 *
716 * @since 1.1.0
717 *
718 * @param [type] $name [description]
719 * @return [type] [description]
720 */
721 public function getEntryData( $name ) {
722 $dir = strtoupper( dirname( $name ) );
723 $name = strtoupper( basename( $name ) );
724 foreach ( $this->package['entries'] as $entry ) {
725 if ( strtoupper( $entry['path'] ) === $dir && strtoupper( $entry['name'] ) === $name ) {
726 return $entry['data'];
727 }
728 }
729 $this->error( 'Entry not found: ' . $name );
730 return false;
731 }
732
733 /**
734 * [getEntryXML description]
735 *
736 * @since 1.1.0
737 *
738 * @param [type] $name [description]
739 * @return [type] [description]
740 */
741 public function getEntryXML( $name ) {
742 if ( $entry_xml = $this->getEntryData( $name ) ) {
743 // Remove dirty namespace prefixes.
744 $entry_xml = preg_replace( '/xmlns[^=]*="[^"]*"/i', '', $entry_xml ); // remove namespaces
745 $entry_xml = preg_replace( '/[a-zA-Z0-9]+:([a-zA-Z0-9]+="[^"]+")/', '$1$2', $entry_xml ); // remove namespaced attrs
746 $entry_xml = preg_replace( '/<[a-zA-Z0-9]+:([^>]+)>/', '<$1>', $entry_xml ); // fix namespaced opened tags
747 $entry_xml = preg_replace( '/<\/[a-zA-Z0-9]+:([^>]+)>/', '</$1>', $entry_xml ); // fix namespaced closed tags
748
749 // XML External Entity (XXE) Prevention.
750 $_old_value = libxml_disable_entity_loader( true );
751 $entry_xmlobj = simplexml_load_string( $entry_xml );
752 libxml_disable_entity_loader( $_old_value );
753 if ( $entry_xmlobj ) {
754 return $entry_xmlobj;
755 }
756 $e = libxml_get_last_error();
757 $this->error( 'XML-entry ' . $name . ' parser error ' . $e->message . ' line ' . $e->line );
758 } else {
759 $this->error( 'XML-entry not found: ' . $name );
760 }
761 return false;
762 }
763
764 /**
765 * [unixstamp description]
766 *
767 * @since 1.1.0
768 *
769 * @param [type] $excelDateTime [description]
770 * @return [type] [description]
771 */
772 public function unixstamp( $excelDateTime ) {
773 $d = floor( $excelDateTime ); // seconds since 1900
774 $t = $excelDateTime - $d;
775
776 return ( abs( $d ) > 0 ) ? ( $d - 25569 ) * DAY_IN_SECONDS + round( $t * DAY_IN_SECONDS ) : round( $t * DAY_IN_SECONDS ); // 25569 days = 70 years?
777 }
778
779 /**
780 * [error description]
781 *
782 * @since 1.1.0
783 *
784 * @param string $set Optional. [description]
785 * @return [type] [description]
786 */
787 public function error( $set = '' ) {
788 if ( '' !== $set ) {
789 $this->error = $set;
790 // trigger_error( __CLASS__ . ': ' . $set, E_USER_WARNING );
791 }
792 return $this->error;
793 }
794
795 /**
796 * [success description]
797 *
798 * @since 1.1.0
799 *
800 * @return [type] [description]
801 */
802 public function success() {
803 return ! $this->error;
804 }
805
806 /**
807 * [_parse description]
808 *
809 * @since 1.1.0
810 *
811 * @return [type] [description]
812 */
813 protected function _parse() {
814 // Document data holders.
815 $this->sharedstrings = array();
816 $this->sheets = array();
817 // $this->styles = array();
818
819 // Read relations and search for officeDocument.
820 if ( $relations = $this->getEntryXML( '_rels/.rels' ) ) {
821 foreach ( $relations->Relationship as $rel ) {
822 $rel_type = trim( (string) $rel['Type'] );
823 $rel_target = trim( (string) $rel['Target'] );
824 if ( self::SCHEMA_REL_OFFICEDOCUMENT === $rel_type && $this->workbook = $this->getEntryXML( $rel_target ) ) {
825 $index_rId = array(); // [0 => rId1]
826
827 $index = 0;
828 foreach ( $this->workbook->sheets->sheet as $s ) {
829 $this->sheetNames[ $index ] = (string) $s['name'];
830 $index_rId[ $index ] = (string) $s['id'];
831 $index++;
832 }
833
834 if ( $workbookRelations = $this->getEntryXML( dirname( $rel_target ) . '/_rels/workbook.xml.rels' ) ) {
835 // Loop relations for workbook and extract sheets.
836 foreach ( $workbookRelations->Relationship as $workbookRelation ) {
837 $wrel_type = trim( (string) $workbookRelation['Type'] );
838 $wrel_path = dirname( trim( (string) $rel['Target'] ) ) . '/' . trim( $workbookRelation['Target'] );
839 if ( ! $this->entryExists( $wrel_path ) ) {
840 continue;
841 }
842 if ( self::SCHEMA_REL_WORKSHEET === $wrel_type ) {
843 if ( $sheet = $this->getEntryXML( $wrel_path ) ) {
844 $index = array_search( (string) $workbookRelation['Id'], $index_rId, false );
845 $this->sheets[ $index ] = $sheet;
846 }
847 } elseif ( self::SCHEMA_REL_SHAREDSTRINGS === $wrel_type ) {
848 if ( $sharedStrings = $this->getEntryXML( $wrel_path ) ) {
849 foreach ( $sharedStrings->si as $val ) {
850 if ( isset( $val->t ) ) {
851 $this->sharedstrings[] = (string) $val->t;
852 } elseif ( isset( $val->r ) ) {
853 $this->sharedstrings[] = $this->_parseRichText( $val );
854 }
855 }
856 }
857 } elseif ( self::SCHEMA_REL_STYLES === $wrel_type ) {
858 $this->styles = $this->getEntryXML( $wrel_path );
859
860 $nf = array();
861 if ( null !== $this->styles->numFmts->numFmt ) {
862 foreach ( $this->styles->numFmts->numFmt as $v ) {
863 $nf[ (int) $v['numFmtId'] ] = (string) $v['formatCode'];
864 }
865 }
866
867 if ( null !== $this->styles->cellXfs->xf ) {
868 foreach ( $this->styles->cellXfs->xf as $v ) {
869 $v = (array) $v->attributes();
870 $v['format'] = '';
871
872 if ( isset( $v['@attributes']['numFmtId'] ) ) {
873 $v = $v['@attributes'];
874 $fid = (int) $v['numFmtId'];
875 if ( isset( self::$built_in_cell_formats[ $fid ] ) ) {
876 $v['format'] = self::$built_in_cell_formats[ $fid ];
877 } elseif ( isset( $nf[ $fid ] ) ) {
878 $v['format'] = $nf[ $fid ];
879 }
880 }
881 $this->workbook_cell_formats[] = $v;
882 }
883 }
884 }
885 } // foreach
886 break;
887 }
888 }
889 } // foreach
890 }
891
892 // Sort sheets.
893 if ( count( $this->sheets ) ) {
894 ksort( $this->sheets );
895 return true;
896 }
897
898 return false;
899 }
900
901 /**
902 * [_parseRichText description]
903 *
904 * @since 1.1.0
905 *
906 * @param [type] $is [description]
907 * @return string [description]
908 */
909 protected function _parseRichText( $is ) {
910 $value = array();
911
912 if ( isset( $is->t ) ) {
913 $value[] = (string) $is->t;
914 } else {
915 foreach ( $is->r as $run ) {
916 $value[] = (string) $run->t;
917 }
918 }
919
920 return implode( ' ', $value );
921 }
922
923 /**
924 * [parse description]
925 *
926 * @since 1.8.1
927 *
928 * @param [type] $filename [description]
929 * @param bool $is_data [description]
930 * @return [type] [description]
931 */
932 public static function parse( $filename, $is_data = false ) {
933 $xlsx = new self( $filename, $is_data );
934 if ( $xlsx->success() ) {
935 return $xlsx;
936 }
937 self::parse_error( $xlsx->error() );
938
939 return false;
940 }
941
942 /**
943 * [parse_error description]
944 *
945 * @since 1.8.1
946 *
947 * @param string $set [description]
948 * @return [type] [description]
949 */
950 public static function parse_error( $set = '' ) {
951 static $error = '';
952 return ( '' !== $set ) ? $error = $set : $error;
953 }
954
955 /**
956 * [getCell description]
957 *
958 * Example: xlsx->getCell(2,'B87', 0);
959 * Get cell B87 from 2nd worksheet, formatted by General (see $built_in_cell_formats for all formats).
960 * It's useful when we need to get a cell that has the wrong format,
961 * Or just for direct cell reading. (thx EGO7000)
962 *
963 * @since 1.8.1
964 *
965 * @param int $worksheet_index.
966 * @param string $cell.
967 * @param null|int $format.
968 *
969 * @return mixed
970 */
971 public function getCell( $worksheet_index = 0, $cell = 'A1', $format = null ) {
972 if ( false === ( $ws = $this->worksheet( $worksheet_index ) ) ) {
973 return false;
974 }
975
976 list( $current_cell, $current_row ) = is_array( $cell ) ? $cell : $this->_columnIndex( (string) $cell );
977
978 if ( isset( $ws->sheetData->row[ $current_row ], $ws->sheetData->row[ $current_row ]->c[ $current_cell ] ) ) {
979 $c = $ws->sheetData->row[ $current_row ]->c[ $current_cell ];
980 return $this->value( $c, $format );
981 }
982 return null;
983 }
984
985 } // class SimpleXLSX
986