For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
11 KiB

  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2015 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Shared
  23. * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. defined('IDENTIFIER_OLE') ||
  28. define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
  29. class PHPExcel_Shared_OLERead
  30. {
  31. private $data = '';
  32. // OLE identifier
  33. const IDENTIFIER_OLE = IDENTIFIER_OLE;
  34. // Size of a sector = 512 bytes
  35. const BIG_BLOCK_SIZE = 0x200;
  36. // Size of a short sector = 64 bytes
  37. const SMALL_BLOCK_SIZE = 0x40;
  38. // Size of a directory entry always = 128 bytes
  39. const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
  40. // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
  41. const SMALL_BLOCK_THRESHOLD = 0x1000;
  42. // header offsets
  43. const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c;
  44. const ROOT_START_BLOCK_POS = 0x30;
  45. const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c;
  46. const EXTENSION_BLOCK_POS = 0x44;
  47. const NUM_EXTENSION_BLOCK_POS = 0x48;
  48. const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c;
  49. // property storage offsets (directory offsets)
  50. const SIZE_OF_NAME_POS = 0x40;
  51. const TYPE_POS = 0x42;
  52. const START_BLOCK_POS = 0x74;
  53. const SIZE_POS = 0x78;
  54. public $wrkbook = null;
  55. public $summaryInformation = null;
  56. public $documentSummaryInformation = null;
  57. /**
  58. * Read the file
  59. *
  60. * @param $sFileName string Filename
  61. * @throws PHPExcel_Reader_Exception
  62. */
  63. public function read($sFileName)
  64. {
  65. // Check if file exists and is readable
  66. if (!is_readable($sFileName)) {
  67. throw new PHPExcel_Reader_Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable.");
  68. }
  69. // Get the file identifier
  70. // Don't bother reading the whole file until we know it's a valid OLE file
  71. $this->data = file_get_contents($sFileName, false, null, 0, 8);
  72. // Check OLE identifier
  73. if ($this->data != self::IDENTIFIER_OLE) {
  74. throw new PHPExcel_Reader_Exception('The filename ' . $sFileName . ' is not recognised as an OLE file');
  75. }
  76. // Get the file data
  77. $this->data = file_get_contents($sFileName);
  78. // Total number of sectors used for the SAT
  79. $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
  80. // SecID of the first sector of the directory stream
  81. $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
  82. // SecID of the first sector of the SSAT (or -2 if not extant)
  83. $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
  84. // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
  85. $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
  86. // Total number of sectors used by MSAT
  87. $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
  88. $bigBlockDepotBlocks = array();
  89. $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
  90. $bbdBlocks = $this->numBigBlockDepotBlocks;
  91. if ($this->numExtensionBlocks != 0) {
  92. $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4;
  93. }
  94. for ($i = 0; $i < $bbdBlocks; ++$i) {
  95. $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
  96. $pos += 4;
  97. }
  98. for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
  99. $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
  100. $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
  101. for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
  102. $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
  103. $pos += 4;
  104. }
  105. $bbdBlocks += $blocksToRead;
  106. if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
  107. $this->extensionBlock = self::getInt4d($this->data, $pos);
  108. }
  109. }
  110. $pos = 0;
  111. $this->bigBlockChain = '';
  112. $bbs = self::BIG_BLOCK_SIZE / 4;
  113. for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
  114. $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
  115. $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs);
  116. $pos += 4*$bbs;
  117. }
  118. $pos = 0;
  119. $sbdBlock = $this->sbdStartBlock;
  120. $this->smallBlockChain = '';
  121. while ($sbdBlock != -2) {
  122. $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
  123. $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs);
  124. $pos += 4*$bbs;
  125. $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4);
  126. }
  127. // read the directory stream
  128. $block = $this->rootStartBlock;
  129. $this->entry = $this->_readData($block);
  130. $this->readPropertySets();
  131. }
  132. /**
  133. * Extract binary stream data
  134. *
  135. * @return string
  136. */
  137. public function getStream($stream)
  138. {
  139. if ($stream === null) {
  140. return null;
  141. }
  142. $streamData = '';
  143. if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
  144. $rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']);
  145. $block = $this->props[$stream]['startBlock'];
  146. while ($block != -2) {
  147. $pos = $block * self::SMALL_BLOCK_SIZE;
  148. $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
  149. $block = self::getInt4d($this->smallBlockChain, $block*4);
  150. }
  151. return $streamData;
  152. } else {
  153. $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
  154. if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
  155. ++$numBlocks;
  156. }
  157. if ($numBlocks == 0) {
  158. return '';
  159. }
  160. $block = $this->props[$stream]['startBlock'];
  161. while ($block != -2) {
  162. $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
  163. $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
  164. $block = self::getInt4d($this->bigBlockChain, $block*4);
  165. }
  166. return $streamData;
  167. }
  168. }
  169. /**
  170. * Read a standard stream (by joining sectors using information from SAT)
  171. *
  172. * @param int $bl Sector ID where the stream starts
  173. * @return string Data for standard stream
  174. */
  175. private function _readData($bl)
  176. {
  177. $block = $bl;
  178. $data = '';
  179. while ($block != -2) {
  180. $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
  181. $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
  182. $block = self::getInt4d($this->bigBlockChain, $block*4);
  183. }
  184. return $data;
  185. }
  186. /**
  187. * Read entries in the directory stream.
  188. */
  189. private function readPropertySets()
  190. {
  191. $offset = 0;
  192. // loop through entires, each entry is 128 bytes
  193. $entryLen = strlen($this->entry);
  194. while ($offset < $entryLen) {
  195. // entry data (128 bytes)
  196. $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
  197. // size in bytes of name
  198. $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS+1]) << 8);
  199. // type of entry
  200. $type = ord($d[self::TYPE_POS]);
  201. // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
  202. // sectorID of first sector of the short-stream container stream, if this entry is root entry
  203. $startBlock = self::getInt4d($d, self::START_BLOCK_POS);
  204. $size = self::getInt4d($d, self::SIZE_POS);
  205. $name = str_replace("\x00", "", substr($d, 0, $nameSize));
  206. $this->props[] = array(
  207. 'name' => $name,
  208. 'type' => $type,
  209. 'startBlock' => $startBlock,
  210. 'size' => $size
  211. );
  212. // tmp helper to simplify checks
  213. $upName = strtoupper($name);
  214. // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
  215. if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
  216. $this->wrkbook = count($this->props) - 1;
  217. } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
  218. // Root entry
  219. $this->rootentry = count($this->props) - 1;
  220. }
  221. // Summary information
  222. if ($name == chr(5) . 'SummaryInformation') {
  223. // echo 'Summary Information<br />';
  224. $this->summaryInformation = count($this->props) - 1;
  225. }
  226. // Additional Document Summary information
  227. if ($name == chr(5) . 'DocumentSummaryInformation') {
  228. // echo 'Document Summary Information<br />';
  229. $this->documentSummaryInformation = count($this->props) - 1;
  230. }
  231. $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
  232. }
  233. }
  234. /**
  235. * Read 4 bytes of data at specified position
  236. *
  237. * @param string $data
  238. * @param int $pos
  239. * @return int
  240. */
  241. private static function getInt4d($data, $pos)
  242. {
  243. // FIX: represent numbers correctly on 64-bit system
  244. // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
  245. // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
  246. $_or_24 = ord($data[$pos + 3]);
  247. if ($_or_24 >= 128) {
  248. // negative number
  249. $_ord_24 = -abs((256 - $_or_24) << 24);
  250. } else {
  251. $_ord_24 = ($_or_24 & 127) << 24;
  252. }
  253. return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
  254. }
  255. }