~desarrollokumbia/kumbia/0.5

« back to all changes in this revision

Viewing changes to library/excel/OLE.php

  • Committer: Joan Miquel
  • Date: 2008-11-07 01:12:54 UTC
  • Revision ID: joan@ubuntu-black-20081107011254-lc0jk82y3yg7jvpl
commit inicial para kumbia 0.5 RC1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/* vim: set expandtab tabstop=4 shiftwidth=4: */
 
3
// +----------------------------------------------------------------------+
 
4
// | PHP Version 4                                                        |
 
5
// +----------------------------------------------------------------------+
 
6
// | Copyright (c) 1997-2002 The PHP Group                                |
 
7
// +----------------------------------------------------------------------+
 
8
// | This source file is subject to version 2.02 of the PHP license,      |
 
9
// | that is bundled with this package in the file LICENSE, and is        |
 
10
// | available at through the world-wide-web at                           |
 
11
// | http://www.php.net/license/2_02.txt.                                 |
 
12
// | If you did not receive a copy of the PHP license and are unable to   |
 
13
// | obtain it through the world-wide-web, please send a note to          |
 
14
// | license@php.net so we can mail you a copy immediately.               |
 
15
// +----------------------------------------------------------------------+
 
16
// | Author: Xavier Noguer <xnoguer@php.net>                              |
 
17
// | Based on OLE::Storage_Lite by Kawai, Takanori                        |
 
18
// +----------------------------------------------------------------------+
 
19
//
 
20
// $Id: OLE.php,v 1.7 2003/08/21 15:15:40 xnoguer Exp $
 
21
 
 
22
 
 
23
/**
 
24
* Constants for OLE package
 
25
*/
 
26
define('OLE_PPS_TYPE_ROOT',        5);
 
27
define('OLE_PPS_TYPE_DIR',         1);
 
28
define('OLE_PPS_TYPE_FILE',        2);
 
29
define('OLE_DATA_SIZE_SMALL', 0x1000);
 
30
define('OLE_LONG_INT_SIZE',        4);
 
31
define('OLE_PPS_SIZE',          0x80);
 
32
 
 
33
if(!class_exists('OLE_PPS')){
 
34
        require_once 'OLE/PPS.php';
 
35
}
 
36
if(!class_exists('OLE')){
 
37
 
 
38
 
 
39
        /**
 
40
* OLE package base class.
 
41
*
 
42
* @author   Xavier Noguer <xnoguer@php.net>
 
43
* @category Structures
 
44
* @package  OLE
 
45
*/
 
46
        class OLE extends PEAR
 
47
        {
 
48
                /**
 
49
    * The file handle for reading an OLE container
 
50
    * @var resource
 
51
    */
 
52
                var $_file_handle;
 
53
 
 
54
                /**
 
55
    * Array of PPS's found on the OLE container
 
56
    * @var array
 
57
    */
 
58
                var $_list;
 
59
 
 
60
                /**
 
61
    * Creates a new OLE object
 
62
    * Remember to use ampersand when creating an OLE object ($my_ole =& new OLE();)
 
63
    * @access public
 
64
    */
 
65
                function OLE()
 
66
                {
 
67
                        $this->_list = array();
 
68
                }
 
69
 
 
70
                /**
 
71
    * Reads an OLE container from the contents of the file given.
 
72
    *
 
73
    * @acces public
 
74
    * @param string $file
 
75
    * @return mixed true on success, PEAR_Error on failure
 
76
    */
 
77
                function read($file)
 
78
                {
 
79
                        /* consider storing offsets as constants */
 
80
                        $big_block_size_offset = 30;
 
81
                        $iBdbCnt_offset        = 44;
 
82
                        $bd_start_offset       = 68;
 
83
 
 
84
                        $fh = @fopen($file, "r");
 
85
                        if ($fh == false) {
 
86
                                return $this->raiseError("Can't open file $file");
 
87
                        }
 
88
                        $this->_file_handle = $fh;
 
89
 
 
90
                        /* begin reading OLE attributes */
 
91
                        fseek($fh, 0);
 
92
                        $signature = fread($fh, 8);
 
93
                        if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
 
94
                                return $this->raiseError("File doesn't seem to be an OLE container.");
 
95
                        }
 
96
                        fseek($fh, $big_block_size_offset);
 
97
                        $packed_array = unpack("v", fread($fh, 2));
 
98
                        $big_block_size   = pow(2, $packed_array['']);
 
99
 
 
100
                        $packed_array = unpack("v", fread($fh, 2));
 
101
                        $small_block_size = pow(2, $packed_array['']);
 
102
                        $i1stBdL = ($big_block_size - 0x4C) / OLE_LONG_INT_SIZE;
 
103
 
 
104
                        fseek($fh, $iBdbCnt_offset);
 
105
                        $packed_array = unpack("V", fread($fh, 4));
 
106
                        $iBdbCnt = $packed_array[''];
 
107
 
 
108
                        $packed_array = unpack("V", fread($fh, 4));
 
109
                        $pps_wk_start = $packed_array[''];
 
110
 
 
111
                        fseek($fh, $bd_start_offset);
 
112
                        $packed_array = unpack("V", fread($fh, 4));
 
113
                        $bd_start = $packed_array[''];
 
114
                        $packed_array = unpack("V", fread($fh, 4));
 
115
                        $bd_count = $packed_array[''];
 
116
                        $packed_array = unpack("V", fread($fh, 4));
 
117
                        $iAll = $packed_array[''];  // this may be wrong
 
118
                        /* create OLE_PPS objects from */
 
119
                        $ret = $this->_readPpsWks($pps_wk_start, $big_block_size);
 
120
                        if (PEAR::isError($ret)) {
 
121
                                return $ret;
 
122
                        }
 
123
                        return true;
 
124
                }
 
125
 
 
126
                /**
 
127
    * Destructor (using PEAR)
 
128
    * Just closes the file handle on the OLE file.
 
129
    *
 
130
    * @access private
 
131
    */
 
132
                function _OLE()
 
133
                {
 
134
                        fclose($this->_file_handle);
 
135
                }
 
136
 
 
137
                /**
 
138
    * Gets information about all PPS's on the OLE container from the PPS WK's
 
139
    * creates an OLE_PPS object for each one.
 
140
    *
 
141
    * @access private
 
142
    * @param integer $pps_wk_start   Position inside the OLE file where PPS WK's start
 
143
    * @param integer $big_block_size Size of big blobks in the OLE file
 
144
    * @return mixed true on success, PEAR_Error on failure
 
145
    */
 
146
                function _readPpsWks($pps_wk_start, $big_block_size)
 
147
                {
 
148
                        $pointer = ($pps_wk_start + 1) * $big_block_size;
 
149
                        while (1)
 
150
                        {
 
151
                                fseek($this->_file_handle, $pointer);
 
152
                                $pps_wk = fread($this->_file_handle, OLE_PPS_SIZE);
 
153
                                if (strlen($pps_wk) != OLE_PPS_SIZE) {
 
154
                                        break; // Excel likes to add a trailing byte sometimes
 
155
                                        //return $this->raiseError("PPS at $pointer seems too short: ".strlen($pps_wk));
 
156
                                }
 
157
                                $name_length = unpack("c", substr($pps_wk, 64, 2)); // FIXME (2 bytes??)
 
158
                                $name_length = $name_length[''] - 2;
 
159
                                $name = substr($pps_wk, 0, $name_length);
 
160
                                $type = unpack("c", substr($pps_wk, 66, 1));
 
161
                                if (($type[''] != OLE_PPS_TYPE_ROOT) and
 
162
                                ($type[''] != OLE_PPS_TYPE_DIR) and
 
163
                                ($type[''] != OLE_PPS_TYPE_FILE))
 
164
                                {
 
165
                                        return $this->raiseError("PPS at $pointer has unknown type: {$type['']}");
 
166
                                }
 
167
                                $prev = unpack("V", substr($pps_wk, 68, 4));
 
168
                                $next = unpack("V", substr($pps_wk, 72, 4));
 
169
                                $dir  = unpack("V", substr($pps_wk, 76, 4));
 
170
                                // there is no magic number, it can take different values.
 
171
                                //$magic = unpack("V", strrev(substr($pps_wk, 92, 4)));
 
172
                                $time_1st = substr($pps_wk, 100, 8);
 
173
                                $time_2nd = substr($pps_wk, 108, 8);
 
174
                                $start_block = unpack("V", substr($pps_wk, 116, 4));
 
175
                                $size = unpack("V", substr($pps_wk, 120, 4));
 
176
                                // _data member will point to position in file!!
 
177
                                // OLE_PPS object is created with an empty children array!!
 
178
                                $this->_list[] = new OLE_PPS(null, '', $type[''], $prev[''], $next[''],
 
179
                                $dir[''], OLE::OLE2LocalDate($time_1st),
 
180
                                OLE::OLE2LocalDate($time_2nd),
 
181
                                ($start_block[''] + 1) * $big_block_size, array());
 
182
                                // give it a size
 
183
                                $this->_list[count($this->_list) - 1]->Size = $size[''];
 
184
                                // check if the PPS tree (starting from root) is complete
 
185
                                if ($this->_ppsTreeComplete(0)) {
 
186
                                        break;
 
187
                                }
 
188
                                $pointer += OLE_PPS_SIZE;
 
189
                        }
 
190
                }
 
191
 
 
192
                /**
 
193
    * It checks whether the PPS tree is complete (all PPS's read)
 
194
    * starting with the given PPS (not necessarily root)
 
195
    *
 
196
    * @access private
 
197
    * @param integer $index The index of the PPS from which we are checking
 
198
    * @return boolean Whether the PPS tree for the given PPS is complete
 
199
    */
 
200
                function _ppsTreeComplete($index)
 
201
                {
 
202
                        if ($this->_list[$index]->NextPps != -1) {
 
203
                                if (!isset($this->_list[$this->_list[$index]->NextPps])) {
 
204
                                        return false;
 
205
                                }
 
206
                                else {
 
207
                                        return $this->_ppsTreeComplete($this->_list[$index]->NextPps);
 
208
                                }
 
209
                        }
 
210
                        if ($this->_list[$index]->DirPps != -1) {
 
211
                                if (!isset($this->_list[$this->_list[$index]->DirPps])) {
 
212
                                        return false;
 
213
                                }
 
214
                                else {
 
215
                                        return $this->_ppsTreeComplete($this->_list[$index]->DirPps);
 
216
                                }
 
217
                        }
 
218
                        return true;
 
219
                }
 
220
 
 
221
                /**
 
222
    * Checks whether a PPS is a File PPS or not.
 
223
    * If there is no PPS for the index given, it will return false.
 
224
    *
 
225
    * @access public
 
226
    * @param integer $index The index for the PPS
 
227
    * @return bool true if it's a File PPS, false otherwise
 
228
    */
 
229
                function isFile($index)
 
230
                {
 
231
                        if (isset($this->_list[$index])) {
 
232
                                return ($this->_list[$index]->Type == OLE_PPS_TYPE_FILE);
 
233
                        }
 
234
                        return false;
 
235
                }
 
236
 
 
237
                /**
 
238
    * Checks whether a PPS is a Root PPS or not.
 
239
    * If there is no PPS for the index given, it will return false.
 
240
    *
 
241
    * @access public
 
242
    * @param integer $index The index for the PPS.
 
243
    * @return bool true if it's a Root PPS, false otherwise
 
244
    */
 
245
                function isRoot($index)
 
246
                {
 
247
                        if (isset($this->_list[$index])) {
 
248
                                return ($this->_list[$index]->Type == OLE_PPS_TYPE_ROOT);
 
249
                        }
 
250
                        return false;
 
251
                }
 
252
 
 
253
                /**
 
254
    * Gives the total number of PPS's found in the OLE container.
 
255
    *
 
256
    * @access public
 
257
    * @return integer The total number of PPS's found in the OLE container
 
258
    */
 
259
                function ppsTotal()
 
260
                {
 
261
                        return count($this->_list);
 
262
                }
 
263
 
 
264
                /**
 
265
    * Gets data from a PPS
 
266
    * If there is no PPS for the index given, it will return an empty string.
 
267
    *
 
268
    * @access public
 
269
    * @param integer $index    The index for the PPS
 
270
    * @param integer $position The position from which to start reading
 
271
    *                          (relative to the PPS)
 
272
    * @param integer $length   The amount of bytes to read (at most)
 
273
    * @return string The binary string containing the data requested
 
274
    */
 
275
                function getData($index, $position, $length)
 
276
                {
 
277
                        // if position is not valid return empty string
 
278
                        if (!isset($this->_list[$index]) or ($position >= $this->_list[$index]->Size) or ($position < 0)) {
 
279
                                return '';
 
280
                        }
 
281
                        // Beware!!! _data member is actually a position
 
282
                        fseek($this->_file_handle, $this->_list[$index]->_data + $position);
 
283
                        return fread($this->_file_handle, $length);
 
284
                }
 
285
 
 
286
                /**
 
287
    * Gets the data length from a PPS
 
288
    * If there is no PPS for the index given, it will return 0.
 
289
    *
 
290
    * @access public
 
291
    * @param integer $index    The index for the PPS
 
292
    * @return integer The amount of bytes in data the PPS has
 
293
    */
 
294
                function getDataLength($index)
 
295
                {
 
296
                        if (isset($this->_list[$index])) {
 
297
                                return $this->_list[$index]->Size;
 
298
                        }
 
299
                        return 0;
 
300
                }
 
301
 
 
302
                /**
 
303
    * Utility function to transform ASCII text to Unicode
 
304
    *
 
305
    * @access public
 
306
    * @static
 
307
    * @param string $ascii The ASCII string to transform
 
308
    * @return string The string in Unicode
 
309
    */
 
310
                function Asc2Ucs($ascii)
 
311
                {
 
312
                        $rawname = '';
 
313
                        for ($i = 0; $i < strlen($ascii); $i++) {
 
314
                                $rawname .= $ascii{$i}."\x00";
 
315
                        }
 
316
                        return $rawname;
 
317
                }
 
318
 
 
319
                /**
 
320
    * Utility function
 
321
    * Returns a string for the OLE container with the date given
 
322
    *
 
323
    * @access public
 
324
    * @static
 
325
    * @param integer $date A timestamp 
 
326
    * @return string The string for the OLE container
 
327
    */
 
328
                function LocalDate2OLE($date = null)
 
329
                {
 
330
                        if (!isset($date)) {
 
331
                                return "\x00\x00\x00\x00\x00\x00\x00\x00";
 
332
                        }
 
333
 
 
334
                        // factor used for separating numbers into 4 bytes parts
 
335
                        $factor = pow(2,32);
 
336
 
 
337
                        // days from 1-1-1601 until the beggining of UNIX era
 
338
                        $days = 134774;
 
339
                        // calculate seconds
 
340
                        $big_date = $days*24*3600 + gmmktime(date("H",$date),date("i",$date),date("s",$date),
 
341
                        date("m",$date),date("d",$date),date("Y",$date));
 
342
                        // multiply just to make MS happy
 
343
                        $big_date *= 10000000;
 
344
 
 
345
                        $high_part = floor($big_date/$factor);
 
346
                        // lower 4 bytes
 
347
                        $low_part = floor((($big_date/$factor) - $high_part)*$factor);
 
348
 
 
349
                        // Make HEX string
 
350
                        $res = '';
 
351
 
 
352
                        for ($i=0; $i<4; $i++)
 
353
                        {
 
354
                                $hex = $low_part % 0x100;
 
355
                                $res .= pack('c', $hex);
 
356
                                $low_part /= 0x100;
 
357
                        }
 
358
                        for ($i=0; $i<4; $i++)
 
359
                        {
 
360
                                $hex = $high_part % 0x100;
 
361
                                $res .= pack('c', $hex);
 
362
                                $high_part /= 0x100;
 
363
                        }
 
364
                        return $res;
 
365
                }
 
366
 
 
367
                /**
 
368
    * Returns a timestamp from an OLE container's date
 
369
    *
 
370
    * @access public
 
371
    * @static
 
372
    * @param integer $string A binary string with the encoded date
 
373
    * @return string The timestamp corresponding to the string
 
374
    */
 
375
                function OLE2LocalDate($string)
 
376
                {
 
377
                        if (strlen($string) != 8) {
 
378
                                return new PEAR_Error("Expecting 8 byte string");
 
379
                        }
 
380
 
 
381
                        // factor used for separating numbers into 4 bytes parts
 
382
                        $factor = pow(2,32);
 
383
                        $high_part = 0;
 
384
                        for ($i=0; $i<4; $i++)
 
385
                        {
 
386
                                $al = unpack('C', $string{(7 - $i)});
 
387
                                $high_part += $al[''];
 
388
                                if ($i < 3) {
 
389
                                        $high_part *= 0x100;
 
390
                                }
 
391
                        }
 
392
                        $low_part = 0;
 
393
                        for ($i=4; $i<8; $i++)
 
394
                        {
 
395
                                $al = unpack('C', $string{(7 - $i)});
 
396
                                $low_part += $al[''];
 
397
                                if ($i < 7) {
 
398
                                        $low_part *= 0x100;
 
399
                                }
 
400
                        }
 
401
                        $big_date = ($high_part*$factor) + $low_part;
 
402
                        // translate to seconds
 
403
                        $big_date /= 10000000;
 
404
 
 
405
                        // days from 1-1-1601 until the beggining of UNIX era
 
406
                        $days = 134774;
 
407
 
 
408
                        // translate to seconds from beggining of UNIX era
 
409
                        $big_date -= $days*24*3600;
 
410
                        return floor($big_date);
 
411
                }
 
412
        }
 
413
}
 
414
?>