PHPExcel_Shared_OLE
[ class tree: PHPExcel_Shared_OLE ] [ index: PHPExcel_Shared_OLE ] [ all elements ]

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

Documentation generated on Mon, 11 Jan 2010 08:12:18 +0100 by phpDocumentor 1.4.1