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

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