File Source for OLE.php

PHPExcel_Shared_OLE

# Source for file OLE.php

Documentation is available at OLE.php

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.13 2007/03/07 14:38:25 schmidt Exp $
23. /**
24. * Array for storing OLE instances that are accessed from
25. * OLE_ChainedBlockStream::stream_open().
26. * @var array
27. */
28. $GLOBALS['_OLE_INSTANCES'] = array();
30. /**
31. * OLE package base class.
32. *
33. * @author Xavier Noguer <xnoguer@php.net>
34. * @author Christian Schmidt <schmidt@php.net>
35. * @category PHPExcel
36. * @package PHPExcel_Shared_OLE
37. */
38. class PHPExcel_Shared_OLE
39. {
40. const OLE_PPS_TYPE_ROOT = 5;
41. const OLE_PPS_TYPE_DIR = 1;
42. const OLE_PPS_TYPE_FILE = 2;
43. const OLE_DATA_SIZE_SMALL = 0x1000;
44. const OLE_LONG_INT_SIZE = 4;
45. const OLE_PPS_SIZE = 0x80;
47. /**
48. * The file handle for reading an OLE container
49. * @var resource
50. */
51. public $_file_handle;
53. /**
54. * Array of PPS's found on the OLE container
55. * @var array
56. */
57. public $_list = array();
59. /**
60. * Root directory of OLE container
61. * @var OLE_PPS_Root
62. */
63. public $root;
65. /**
66. * Big Block Allocation Table
67. * @var array (blockId => nextBlockId)
68. */
69. public $bbat;
71. /**
72. * Short Block Allocation Table
73. * @var array (blockId => nextBlockId)
74. */
75. public $sbat;
77. /**
78. * Size of big blocks. This is usually 512.
79. * @var int number of octets per block.
80. */
81. public $bigBlockSize;
83. /**
84. * Size of small blocks. This is usually 64.
85. * @var int number of octets per block
86. */
87. public $smallBlockSize;
89. /**
90. * Reads an OLE container from the contents of the file given.
91. *
92. * @acces public
93. * @param string $file
94. * @return mixed true on success, PEAR_Error on failure
95. */
96. public function read($file)
97. {
98. $fh = fopen($file, "r");
99. if (!$fh) {
100. throw new Exception("Can't open file $file");
101. }
102. $this->_file_handle = $fh;
104. $signature = fread($fh, 8);
105. if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
106. throw new Exception("File doesn't seem to be an OLE container.");
107. }
108. fseek($fh, 28);
109. if ( fread($fh, 2) != "\xFE\xFF") {
110. // This shouldn't be a problem in practice
111. throw new Exception("Only Little-Endian encoding is supported.");
112. }
113. // Size of blocks and short blocks in bytes
114. $this-> bigBlockSize = pow(2, self::_readInt2($fh));
115. $this-> smallBlockSize = pow(2, self::_readInt2($fh));
117. // Skip UID, revision number and version number
118. fseek($fh, 44);
119. // Number of blocks in Big Block Allocation Table
120. $bbatBlockCount = self::_readInt4($fh);
122. // Root chain 1st block
123. $directoryFirstBlockId = self::_readInt4($fh);
125. // Skip unused bytes
126. fseek($fh, 56);
127. // Streams shorter than this are stored using small blocks
128. $this->bigBlockThreshold = self::_readInt4($fh);
129. // Block id of first sector in Short Block Allocation Table
130. $sbatFirstBlockId = self::_readInt4($fh);
131. // Number of blocks in Short Block Allocation Table
132. $sbbatBlockCount = self::_readInt4($fh);
133. // Block id of first sector in Master Block Allocation Table
134. $mbatFirstBlockId = self::_readInt4($fh);
135. // Number of blocks in Master Block Allocation Table
136. $mbbatBlockCount = self::_readInt4($fh);
137. $this-> bbat = array();
139. // Remaining 4 * 109 bytes of current block is beginning of Master
140. // Block Allocation Table
141. $mbatBlocks = array();
142. for ($i = 0; $i < 109; ++$i) {
143. $mbatBlocks[] = self::_readInt4($fh);
144. }
146. // Read rest of Master Block Allocation Table (if any is left)
147. $pos = $this->_getBlockOffset($mbatFirstBlockId);
148. for ($i = 0; $i < $mbbatBlockCount; ++$i) {
149. fseek($fh, $pos);
150. for ($j = 0; $j < $this-> bigBlockSize / 4 - 1; ++$j) {
151. $mbatBlocks[] = self::_readInt4($fh);
152. }
153. // Last block id in each block points to next block
154. $pos = $this->_getBlockOffset(self::_readInt4($fh));
155. }
157. // Read Big Block Allocation Table according to chain specified by
158. // $mbatBlocks
159. for ($i = 0; $i < $bbatBlockCount; ++$i) {
160. $pos = $this->_getBlockOffset($mbatBlocks[$i]);
161. fseek($fh, $pos);
162. for ($j = 0 ; $j < $this-> bigBlockSize / 4; ++$j) {
163. $this-> bbat[] = self::_readInt4($fh);
164. }
165. }
167. // Read short block allocation table (SBAT)
168. $this-> sbat = array();
169. $shortBlockCount = $sbbatBlockCount * $this-> bigBlockSize / 4;
170. $sbatFh = $this-> getStream($sbatFirstBlockId);
171. for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
172. $this-> sbat[$blockId] = self::_readInt4($sbatFh);
173. }
174. fclose($sbatFh);
176. $this->_readPpsWks($directoryFirstBlockId);
178. return true;
179. }
181. /**
182. * @param int block id
183. * @param int byte offset from beginning of file
184. * @access public
185. */
186. public function _getBlockOffset($blockId)
187. {
188. return 512 + $blockId * $this-> bigBlockSize;
189. }
191. /**
192. * Returns a stream for use with fread() etc. External callers should
193. * use PHPExcel_Shared_OLE_PPS_File::getStream().
194. * @param int|PPS block id or PPS
195. * @return resource read-only stream
196. */
197. public function getStream($blockIdOrPps)
198. {
199. static $isRegistered = false;
200. if (!$isRegistered) {
201. stream_wrapper_register('ole-chainedblockstream',
202. 'PHPExcel_Shared_OLE_ChainedBlockStream');
203. $isRegistered = true;
204. }
206. // Store current instance in global array, so that it can be accessed
207. // in OLE_ChainedBlockStream::stream_open().
208. // Object is removed from self::$instances in OLE_Stream::close().
209. $GLOBALS['_OLE_INSTANCES'][] = $this;
210. $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
212. $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
213. if ($blockIdOrPps instanceof PHPExcel_Shared_OLE_PPS) {
214. $path .= '&blockId=' . $blockIdOrPps->_StartBlock;
215. $path .= '&size=' . $blockIdOrPps->Size;
216. } else {
217. $path .= '&blockId=' . $blockIdOrPps;
218. }
219. return fopen($path, 'r');
220. }
222. /**
223. * Reads a signed char.
224. * @param resource file handle
225. * @return int
226. * @access public
227. */
228. private static function _readInt1($fh)
229. {
230. list(, $tmp) = unpack("c", fread($fh, 1));
231. return $tmp;
232. }
234. /**
235. * Reads an unsigned short (2 octets).
236. * @param resource file handle
237. * @return int
238. * @access public
239. */
240. private static function _readInt2($fh)
241. {
242. list(, $tmp) = unpack("v", fread($fh, 2));
243. return $tmp;
244. }
246. /**
247. * Reads an unsigned long (4 octets).
248. * @param resource file handle
249. * @return int
250. * @access public
251. */
252. private static function _readInt4($fh)
253. {
254. list(, $tmp) = unpack("V", fread($fh, 4));
255. return $tmp;
256. }
258. /**
259. * Gets information about all PPS's on the OLE container from the PPS WK's
260. * creates an OLE_PPS object for each one.
261. *
262. * @access public
263. * @param integer the block id of the first block
264. * @return mixed true on success, PEAR_Error on failure
265. */
266. public function _readPpsWks($blockId)
267. {
268. $fh = $this-> getStream($blockId);
269. for ($pos = 0; ; $pos += 128) {
270. fseek($fh, $pos, SEEK_SET);
271. $nameUtf16 = fread($fh, 64);
272. $nameLength = self::_readInt2($fh);
273. $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
274. // Simple conversion from UTF-16LE to ISO-8859-1
275. $name = str_replace("\x00", "", $nameUtf16);
276. $type = self::_readInt1($fh);
277. switch ($type) {
278. case self:: OLE_PPS_TYPE_ROOT:
279. $pps = new PHPExcel_Shared_OLE_PPS_Root(null, null, array());
280. $this-> root = $pps;
281. break;
282. case self:: OLE_PPS_TYPE_DIR:
283. $pps = new PHPExcel_Shared_OLE_PPS(null, null, null, null, null,
284. null, null, null, null, array());
285. break;
286. case self:: OLE_PPS_TYPE_FILE:
287. $pps = new PHPExcel_Shared_OLE_PPS_File($name);
288. break;
289. default:
290. continue;
291. }
292. fseek($fh, 1, SEEK_CUR);
293. $pps->Type = $type;
294. $pps->Name = $name;
295. $pps->PrevPps = self::_readInt4($fh);
296. $pps->NextPps = self::_readInt4($fh);
297. $pps->DirPps = self::_readInt4($fh);
298. fseek($fh, 20, SEEK_CUR);
299. $pps->Time1st = self:: OLE2LocalDate( fread($fh, 8));
300. $pps->Time2nd = self:: OLE2LocalDate( fread($fh, 8));
301. $pps->_StartBlock = self::_readInt4($fh);
302. $pps->Size = self::_readInt4($fh);
303. $pps->No = count($this->_list);
304. $this->_list[] = $pps;
306. // check if the PPS tree (starting from root) is complete
307. if (isset($this-> root) &&
308. $this->_ppsTreeComplete($this-> root->No)) {
310. break;
311. }
312. }
313. fclose($fh);
315. // Initialize $pps->children on directories
316. foreach ($this->_list as $pps) {
317. if ($pps->Type == self:: OLE_PPS_TYPE_DIR || $pps->Type == self:: OLE_PPS_TYPE_ROOT) {
318. $nos = array($pps->DirPps);
319. $pps->children = array();
320. while ($nos) {
321. $no = array_pop($nos);
322. if ($no != -1) {
323. $childPps = $this->_list[$no];
324. $nos[] = $childPps->PrevPps;
325. $nos[] = $childPps->NextPps;
326. $pps->children[] = $childPps;
327. }
328. }
329. }
330. }
332. return true;
333. }
335. /**
336. * It checks whether the PPS tree is complete (all PPS's read)
337. * starting with the given PPS (not necessarily root)
338. *
339. * @access public
340. * @param integer $index The index of the PPS from which we are checking
341. * @return boolean Whether the PPS tree for the given PPS is complete
342. */
343. public function _ppsTreeComplete($index)
344. {
345. return isset($this->_list[$index]) &&
346. ($pps = $this->_list[$index]) &&
347. ($pps->PrevPps == -1 ||
348. $this->_ppsTreeComplete($pps->PrevPps)) &&
349. ($pps->NextPps == -1 ||
350. $this->_ppsTreeComplete($pps->NextPps)) &&
351. ($pps->DirPps == -1 ||
352. $this->_ppsTreeComplete($pps->DirPps));
353. }
355. /**
356. * Checks whether a PPS is a File PPS or not.
357. * If there is no PPS for the index given, it will return false.
358. *
359. * @access public
360. * @param integer $index The index for the PPS
361. * @return bool true if it's a File PPS, false otherwise
362. */
363. public function isFile($index)
364. {
365. if (isset($this->_list[$index])) {
366. return ($this->_list[$index]->Type == self:: OLE_PPS_TYPE_FILE);
367. }
368. return false;
369. }
371. /**
372. * Checks whether a PPS is a Root PPS or not.
373. * If there is no PPS for the index given, it will return false.
374. *
375. * @access public
376. * @param integer $index The index for the PPS.
377. * @return bool true if it's a Root PPS, false otherwise
378. */
379. public function isRoot($index)
380. {
381. if (isset($this->_list[$index])) {
382. return ($this->_list[$index]->Type == self:: OLE_PPS_TYPE_ROOT);
383. }
384. return false;
385. }
387. /**
388. * Gives the total number of PPS's found in the OLE container.
389. *
390. * @access public
391. * @return integer The total number of PPS's found in the OLE container
392. */
393. public function ppsTotal()
394. {
395. return count($this->_list);
396. }
398. /**
399. * Gets data from a PPS
400. * If there is no PPS for the index given, it will return an empty string.
401. *
402. * @access public
403. * @param integer $index The index for the PPS
404. * @param integer $position The position from which to start reading
405. * (relative to the PPS)
406. * @param integer $length The amount of bytes to read (at most)
407. * @return string The binary string containing the data requested
408. * @see OLE_PPS_File::getStream()
409. */
410. public function getData($index, $position, $length)
411. {
412. // if position is not valid return empty string
413. if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
414. return '';
415. }
416. $fh = $this-> getStream($this->_list[$index]);
417. $data = stream_get_contents($fh, $length, $position);
418. fclose($fh);
419. return $data;
420. }
422. /**
423. * Gets the data length from a PPS
424. * If there is no PPS for the index given, it will return 0.
425. *
426. * @access public
427. * @param integer $index The index for the PPS
428. * @return integer The amount of bytes in data the PPS has
429. */
430. public function getDataLength($index)
431. {
432. if (isset($this->_list[$index])) {
433. return $this->_list[$index]->Size;
434. }
435. return 0;
436. }
438. /**
439. * Utility function to transform ASCII text to Unicode
440. *
441. * @access public
442. * @static
443. * @param string $ascii The ASCII string to transform
444. * @return string The string in Unicode
445. */
446. public static function Asc2Ucs($ascii)
447. {
448. $rawname = '';
449. for ($i = 0; $i < strlen($ascii); ++$i) {
450. $rawname .= $ascii{$i} . "\x00";
451. }
452. return $rawname;
453. }
455. /**
456. * Utility function
457. * Returns a string for the OLE container with the date given
458. *
459. * @access public
460. * @static
461. * @param integer $date A timestamp
462. * @return string The string for the OLE container
463. */
464. public static function LocalDate2OLE($date = null)
465. {
466. if (!isset($date)) {
467. return "\x00\x00\x00\x00\x00\x00\x00\x00";
468. }
470. // factor used for separating numbers into 4 bytes parts
471. $factor = pow(2, 32);
473. // days from 1-1-1601 until the beggining of UNIX era
474. $days = 134774;
475. // calculate seconds
476. $big_date = $days*24*3600 + gmmktime( date("H",$date), date("i",$date), date("s",$date),
477. date("m",$date), date("d",$date), date("Y",$date));
478. // multiply just to make MS happy
479. $big_date *= 10000000;
481. $high_part = floor($big_date / $factor);
482. // lower 4 bytes
483. $low_part = floor((($big_date / $factor) - $high_part) * $factor);
485. // Make HEX string
486. $res = '';
488. for ($i = 0; $i < 4; ++$i) {
489. $hex = $low_part % 0x100;
490. $res .= pack('c', $hex);
491. $low_part /= 0x100;
492. }
493. for ($i = 0; $i < 4; ++$i) {
494. $hex = $high_part % 0x100;
495. $res .= pack('c', $hex);
496. $high_part /= 0x100;
497. }
498. return $res;
499. }
501. /**
502. * Returns a timestamp from an OLE container's date
503. *
504. * @access public
505. * @static
506. * @param integer $string A binary string with the encoded date
507. * @return string The timestamp corresponding to the string
508. */
509. public static function OLE2LocalDate($string)
510. {
511. if ( strlen($string) != 8) {
512. return new PEAR_Error("Expecting 8 byte string");
513. }
515. // factor used for separating numbers into 4 bytes parts
516. $factor = pow(2,32);
517. list(, $high_part) = unpack('V', substr($string, 4, 4));
518. list(, $low_part) = unpack('V', substr($string, 0, 4));
520. $big_date = ($high_part * $factor) + $low_part;
521. // translate to seconds
522. $big_date /= 10000000;
524. // days from 1-1-1601 until the beggining of UNIX era
525. $days = 134774;
527. // translate to seconds from beggining of UNIX era
528. $big_date -= $days * 24 * 3600;
529. return floor($big_date);
530. }
531. }

Documentation generated on Sun, 27 Feb 2011 16:32:53 -0800 by phpDocumentor 1.4.3