source: spip-zone/_plugins_/fulltext/trunk/lib/simplexlsx.class.php @ 85892

Last change on this file since 85892 was 85892, checked in by kent1@…, 5 years ago

Indentation

File size: 11.3 KB
Line 
1<?php
2
3if (!defined("_ECRIRE_INC_VERSION")) return;
4
5// Classe SimpleXLSX php v0.4 de Sergey Schuchkin legerement modifiee pour SPIP
6// MS Excel 2007 workbooks reader
7// Example:
8//   $xlsx = new SimpleXLSX('book.xlsx');
9//   print_r( $xlsx->rows() );
10// Example 2:
11//   $xlsx = new SimpleXLSX('book.xlsx');
12//   print_r( $xlsx->rowsEx() );
13// Example 3:
14//   $xlsx = new SimpleXLSX('book.xlsx');
15//   print_r( $xlsx->rows(2) ); // second worksheet
16//
17// 0.4 sheets(), sheetsCount(), unixstamp( $excelDateTime )
18// 0.3 - fixed empty cells (Gonzo patch)
19
20class SimpleXLSX {
21        // Don't remove this string! Created by Sergey Schuchkin from http://www.sibvison.ru - professional php developers team 2010-2011
22        private $sheets;
23        private $hyperlinks;
24        private $package;
25        private $sharedstrings;
26        // scheme
27        const SCHEMA_OFFICEDOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
28        const SCHEMA_RELATIONSHIP = 'http://schemas.openxmlformats.org/package/2006/relationships';
29        const SCHEMA_SHAREDSTRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
30        const SCHEMA_WORKSHEETRELATION = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
31
32        function __construct($filename) {
33                $this -> _unzip($filename);
34                $this -> _parse();
35        }
36
37        function sheets() {
38                return $this -> sheets;
39        }
40
41        function sheetsCount() {
42                return count($this -> sheets);
43        }
44
45        function worksheet($worksheet_id) {
46                if (isset($this -> sheets[$worksheet_id])) {
47                        $ws = $this -> sheets[$worksheet_id];
48
49                        if (isset($ws -> hyperlinks)) {
50                                $this -> hyperlinks = array();
51                                foreach ($ws->hyperlinks->hyperlink as $hyperlink) {
52                                        $this -> hyperlinks[(string)$hyperlink['ref']] = (string)$hyperlink['display'];
53                                }
54                        }
55
56                        return $ws;
57                }
58
59        }
60
61        function dimension($worksheet_id = 1) {
62                $ws = $this -> worksheet($worksheet_id);
63                $ref = (string)$ws -> dimension['ref'];
64                $d = explode(':', $ref);
65                $index = $this -> _columnIndex($d[1]);
66                return array($index[0] + 1, $index[1] + 1);
67        }
68
69        // sheets numeration: 1,2,3....
70        function rows($worksheet_id = 1) {
71
72                $ws = $this -> worksheet($worksheet_id);
73
74                $rows = array();
75                $curR = 0;
76
77                foreach ($ws->sheetData->row as $row) {
78
79                        foreach ($row->c as $c) {
80                                list($curC, ) = $this -> _columnIndex((string)$c['r']);
81                                $rows[$curR][$curC] = $this -> value($c);
82                        }
83
84                        $curR++;
85                }
86                return $rows;
87        }
88
89        function rowsEx($worksheet_id = 1) {
90                $rows = array();
91                $curR = 0;
92                if (($ws = $this -> worksheet($worksheet_id)) === false)
93                        return false;
94                foreach ($ws->sheetData->row as $row) {
95
96                        foreach ($row->c as $c) {
97                                list($curC, ) = $this -> _columnIndex((string)$c['r']);
98                                $rows[$curR][$curC] = array('name' => (string)$c['r'], 'value' => $this -> value($c), 'href' => $this -> href($c), );
99                        }
100                        $curR++;
101                }
102                return $rows;
103
104        }
105
106        // thx Gonzo
107        function _columnIndex($cell = 'A1') {
108
109                if (preg_match("/([A-Z]+)(\d+)/", $cell, $matches)) {
110
111                        $col = $matches[1];
112                        $row = $matches[2];
113
114                        $colLen = strlen($col);
115                        $index = 0;
116
117                        for ($i = $colLen - 1; $i >= 0; $i--)
118                                $index += (ord($col{$i}) - 64) * pow(26, $colLen - $i - 1);
119
120                        return array($index - 1, $row - 1);
121                }
122
123        }
124
125        function value($cell) {
126                // Determine data type
127                $dataType = (string)$cell["t"];
128                switch ($dataType) {
129                        case "s" :
130                                // Value is a shared string
131                                if ((string)$cell -> v != '') {
132                                        $value = $this -> sharedstrings[intval($cell -> v)];
133                                } else {
134                                        $value = '';
135                                }
136
137                                break;
138
139                        case "b" :
140                                // Value is boolean
141                                $value = (string)$cell -> v;
142                                if ($value == '0') {
143                                        $value = false;
144                                } else if ($value == '1') {
145                                        $value = true;
146                                } else {
147                                        $value = (bool)$cell -> v;
148                                }
149
150                                break;
151
152                        case "inlineStr" :
153                                // Value is rich text inline
154                                $value = $this -> _parseRichText($cell -> is);
155
156                                break;
157
158                        case "e" :
159                                // Value is an error message
160                                if ((string)$cell -> v != '') {
161                                        $value = (string)$cell -> v;
162                                } else {
163                                        $value = '';
164                                }
165
166                                break;
167
168                        default :
169                                // Value is a string
170                                $value = (string)$cell -> v;
171
172                                // Check for numeric values
173                                if (is_numeric($value) && $dataType != 's') {
174                                        if ($value == (int)$value)
175                                                $value = (int)$value;
176                                        elseif ($value == (float)$value)
177                                                $value = (float)$value;
178                                        elseif ($value == (double)$value)
179                                                $value = (double)$value;
180                                }
181                }
182                return $value;
183        }
184
185        function href($cell) {
186                return isset($this -> hyperlinks[(string)$cell['r']]) ? $this -> hyperlinks[(string)$cell['r']] : '';
187        }
188
189        function _unzip($filename) {
190                // Clear current file
191                $this -> datasec = array();
192
193                // Package information
194                $this -> package = array('filename' => $filename, 'mtime' => filemtime($filename), 'size' => filesize($filename), 'comment' => '', 'entries' => array());
195                // Read file
196                $oF = fopen($filename, 'rb');
197                $vZ = fread($oF, $this -> package['size']);
198                fclose($oF);
199                // Cut end of central directory
200                $aE = explode("\x50\x4b\x05\x06", $vZ);
201
202                // Normal way
203                $aP = unpack('x16/v1CL', $aE[1]);
204                $this -> package['comment'] = substr($aE[1], 18, $aP['CL']);
205
206                // Translates end of line from other operating systems
207                $this -> package['comment'] = strtr($this -> package['comment'], array("\r\n" => "\n", "\r" => "\n"));
208
209                // Cut the entries from the central directory
210                $aE = explode("\x50\x4b\x01\x02", $vZ);
211                // Explode to each part
212                $aE = explode("\x50\x4b\x03\x04", $aE[0]);
213                // Shift out spanning signature or empty entry
214                array_shift($aE);
215
216                // Loop through the entries
217                foreach ($aE as $vZ) {
218                        $aI = array();
219                        $aI['E'] = 0;
220                        $aI['EM'] = '';
221                        // Retrieving local file header information
222                        //                      $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
223                        $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ);
224                        // Check if data is encrypted
225                        //                      $bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE;
226                        $bE = false;
227                        $nF = $aP['FNL'];
228                        $mF = $aP['EFL'];
229
230                        // Special case : value block after the compressed data
231                        if ($aP['GPF'] & 0x0008) {
232                                $aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
233
234                                $aP['CRC'] = $aP1['CRC'];
235                                $aP['CS'] = $aP1['CS'];
236                                $aP['UCS'] = $aP1['UCS'];
237
238                                $vZ = substr($vZ, 0, -12);
239                        }
240
241                        // Getting stored filename
242                        $aI['N'] = substr($vZ, 26, $nF);
243
244                        if (substr($aI['N'], -1) == '/') {
245                                // is a directory entry - will be skipped
246                                continue;
247                        }
248
249                        // Truncate full filename in path and filename
250                        $aI['P'] = dirname($aI['N']);
251                        $aI['P'] = $aI['P'] == '.' ? '' : $aI['P'];
252                        $aI['N'] = basename($aI['N']);
253
254                        $vZ = substr($vZ, 26 + $nF + $mF);
255
256                        if (strlen($vZ) != $aP['CS']) {
257                                $aI['E'] = 1;
258                                $aI['EM'] = 'Compressed size is not equal with the value in header information.';
259                        } else {
260                                if ($bE) {
261                                        $aI['E'] = 5;
262                                        $aI['EM'] = 'File is encrypted, which is not supported from this class.';
263                                } else {
264                                        switch($aP['CM']) {
265                                                case 0 :
266                                                        // Stored
267                                                        // Here is nothing to do, the file ist flat.
268                                                        break;
269                                                case 8 :
270                                                        // Deflated
271                                                        $vZ = gzinflate($vZ);
272                                                        break;
273                                                case 12 :
274                                                        // BZIP2
275                                                        if (!extension_loaded('bz2')) {
276                                                                if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
277                                                                        @dl('php_bz2.dll');
278                                                                } else {
279                                                                        @dl('bz2.so');
280                                                                }
281                                                        }
282                                                        if (extension_loaded('bz2')) {
283                                                                $vZ = bzdecompress($vZ);
284                                                        } else {
285                                                                $aI['E'] = 7;
286                                                                $aI['EM'] = "PHP BZIP2 extension not available.";
287                                                        }
288                                                        break;
289                                                default :
290                                                        $aI['E'] = 6;
291                                                        $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
292                                        }
293                                        if (!$aI['E']) {
294                                                if ($vZ === FALSE) {
295                                                        $aI['E'] = 2;
296                                                        $aI['EM'] = 'Decompression of data failed.';
297                                                } else {
298                                                        if (strlen($vZ) != $aP['UCS']) {
299                                                                $aI['E'] = 3;
300                                                                $aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
301                                                        } else {
302                                                                if (crc32($vZ) != $aP['CRC']) {
303                                                                        $aI['E'] = 4;
304                                                                        $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
305                                                                }
306                                                        }
307                                                }
308                                        }
309                                }
310                        }
311
312                        $aI['D'] = $vZ;
313
314                        // DOS to UNIX timestamp
315                        $aI['T'] = mktime(($aP['FT'] & 0xf800)>>11, ($aP['FT'] & 0x07e0)>>5, ($aP['FT'] & 0x001f)<<1, ($aP['FD'] & 0x01e0)>>5, ($aP['FD'] & 0x001f), (($aP['FD'] & 0xfe00)>>9) + 1980);
316
317                        //$this->Entries[] = &new SimpleUnzipEntry($aI);
318                        $this -> package['entries'][] = array('data' => $aI['D'], 'error' => $aI['E'], 'error_msg' => $aI['EM'], 'name' => $aI['N'], 'path' => $aI['P'], 'time' => $aI['T']);
319
320                } // end for each entries
321        }
322
323        function getPackage() {
324                return $this -> package;
325        }
326
327        function getEntryData($name) {
328                $dir = dirname($name);
329                $name = basename($name);
330                foreach ($this->package['entries'] as $entry)
331                        if ($entry['path'] == $dir && $entry['name'] == $name)
332                                return $entry['data'];
333        }
334
335        function unixstamp($excelDateTime) {
336                $d = floor($excelDateTime);
337                // seconds since 1900
338                $t = $excelDateTime - $d;
339                return ($d > 0) ? ($d - 25569) * 86400 + $t * 86400 : $t * 86400;
340        }
341
342        function _parse() {
343                // Document data holders
344                $this -> sharedstrings = array();
345                $this -> sheets = array();
346
347                // Read relations and search for officeDocument
348                $relations = simplexml_load_string($this -> getEntryData("_rels/.rels"));
349                foreach ($relations->Relationship as $rel) {
350                        if ($rel["Type"] == SimpleXLSX::SCHEMA_OFFICEDOCUMENT) {
351                                // Found office document! Read relations for workbook...
352                                $workbookRelations = simplexml_load_string($this -> getEntryData(dirname($rel["Target"]) . "/_rels/" . basename($rel["Target"]) . ".rels"));
353                                $workbookRelations -> registerXPathNamespace("rel", SimpleXLSX::SCHEMA_RELATIONSHIP);
354
355                                // Read shared strings
356                                $sharedStringsPath = $workbookRelations -> xpath("rel:Relationship[@Type='" . SimpleXLSX::SCHEMA_SHAREDSTRINGS . "']");
357                                $sharedStringsPath = (string)$sharedStringsPath[0]['Target'];
358                                $xmlStrings = simplexml_load_string($this -> getEntryData(dirname($rel["Target"]) . "/" . $sharedStringsPath));
359                                if (isset($xmlStrings) && isset($xmlStrings -> si)) {
360                                        foreach ($xmlStrings->si as $val) {
361                                                if (isset($val -> t)) {
362                                                        $this -> sharedstrings[] = (string)$val -> t;
363                                                } elseif (isset($val -> r)) {
364                                                        $this -> sharedstrings[] = $this -> _parseRichText($val);
365                                                }
366                                        }
367                                }
368
369                                // Loop relations for workbook and extract sheets...
370                                foreach ($workbookRelations->Relationship as $workbookRelation) {
371                                        if ($workbookRelation["Type"] == SimpleXLSX::SCHEMA_WORKSHEETRELATION) {
372                                                $this -> sheets[str_replace('rId', '', (string)$workbookRelation["Id"])] = simplexml_load_string($this -> getEntryData(dirname($rel["Target"]) . "/" . dirname($workbookRelation["Target"]) . "/" . basename($workbookRelation["Target"])));
373                                        }
374                                }
375
376                                break;
377                        }
378                }
379
380                // Sort sheets
381                ksort($this -> sheets);
382        }
383
384        private function _parseRichText($is = null) {
385                $value = array();
386
387                if (isset($is -> t)) {
388                        $value[] = (string)$is -> t;
389                } else {
390                        foreach ($is->r as $run) {
391                                $value[] = (string)$run -> t;
392                        }
393                }
394
395                return implode(' ', $value);
396        }
397
398}
399?>
Note: See TracBrowser for help on using the repository browser.