src/Service/Simplexlsx.php line 244

Open in your IDE?
  1. <?php
  2. // src/Service/Simplexlsx.php
  3. namespace App\Service;
  4. class Simplexlsx {
  5.     // Don't remove this string! Created by Sergey Schuchkin from http://www.sibvision.ru - professional php developers team 2010-2013
  6.     private $workbook;
  7.     private $sheets;
  8.     private $styles;
  9.     private $hyperlinks;
  10.     private $package = array(
  11.         'filename' => '',
  12.         'mtime' => 0,
  13.         'size' => 0,
  14.         'comment' => '',
  15.         'entries' => array()
  16.     );
  17.     private $sharedstrings;
  18.     private $error false;
  19.     private $debug false;
  20.     // scheme
  21.     const SCHEMA_REL_OFFICEDOCUMENT  =  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
  22.     const SCHEMA_REL_SHAREDSTRINGS =  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
  23.     const SCHEMA_REL_WORKSHEET =  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
  24.     const SCHEMA_REL_STYLES 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
  25.     private $workbook_cell_formats = array();
  26.     private $built_in_cell_formats = array(
  27.         => 'General',
  28.         => '0',
  29.         => '0.00',
  30.         => '#,##0',
  31.         => '#,##0.00',
  32.         => '0%',
  33.         10 => '0.00%',
  34.         11 => '0.00E+00',
  35.         12 => '# ?/?',
  36.         13 => '# ??/??',
  37.         14 => 'mm-dd-yy',
  38.         15 => 'd-mmm-yy',
  39.         16 => 'd-mmm',
  40.         17 => 'mmm-yy',
  41.         18 => 'h:mm AM/PM',
  42.         19 => 'h:mm:ss AM/PM',
  43.         20 => 'h:mm',
  44.         21 => 'h:mm:ss',
  45.         22 => 'm/d/yy h:mm',
  46.         37 => '#,##0 ;(#,##0)',
  47.         38 => '#,##0 ;[Red](#,##0)',
  48.         39 => '#,##0.00;(#,##0.00)',
  49.         40 => '#,##0.00;[Red](#,##0.00)',
  50.         44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
  51.         45 => 'mm:ss',
  52.         46 => '[h]:mm:ss',
  53.         47 => 'mmss.0',
  54.         48 => '##0.0E+0',
  55.         49 => '@',
  56.         27 => '[$-404]e/m/d',
  57.         30 => 'm/d/yy',
  58.         36 => '[$-404]e/m/d',
  59.         50 => '[$-404]e/m/d',
  60.         57 => '[$-404]e/m/d',
  61.         59 => 't0',
  62.         60 => 't0.00',
  63.         61 => 't#,##0',
  64.         62 => 't#,##0.00',
  65.         67 => 't0%',
  66.         68 => 't0.00%',
  67.         69 => 't# ?/?',
  68.         70 => 't# ??/??',
  69.     );
  70.     function __construct$filename$is_data false$debug false ) {
  71.         $this->debug $debug;
  72.         $this->_unzip$filename$is_data );
  73.         $this->_parse();
  74.     }
  75.     function sheets() {
  76.         return $this->sheets;
  77.     }
  78.     function sheetsCount() {
  79.         return count($this->sheets);
  80.     }
  81.     function sheetName$worksheet_id) {
  82.         foreach( $this->workbook->sheets->sheet as $s ) {
  83.             if ( $s->attributes('r',true)->id == 'rId'.$worksheet_id)
  84.                 return (string) $s['name'];
  85.         }
  86.         return false;
  87.     }
  88.     function sheetNames() {
  89.         $result = array();
  90.         foreach( $this->workbook->sheets->sheet as $s ) {
  91.             $resultsubstr$s->attributes('r',true)->id3) ] = (string) $s['name'];
  92.         }
  93.         return $result;
  94.     }
  95.     function worksheet$worksheet_id ) {
  96.         if ( isset( $this->sheets$worksheet_id ] ) ) {
  97.             $ws $this->sheets$worksheet_id ];
  98.             if (isset($ws->hyperlinks)) {
  99.                 $this->hyperlinks = array();
  100.                 foreach( $ws->hyperlinks->hyperlink as $hyperlink ) {
  101.                     $this->hyperlinks[ (string) $hyperlink['ref'] ] = (string) $hyperlink['display'];
  102.                 }
  103.             }
  104.             return $ws;
  105.         } else {
  106.             $this->error'Worksheet '.$worksheet_id.' not found. Try $xlsx->rows('.implode(') or $xlsx->rows('array_keys($this->sheets)).')' );
  107.             return false;
  108.         }
  109.     }
  110.     function dimension$worksheet_id ) {
  111.         if (($ws $this->worksheet$worksheet_id)) === false)
  112.             return false;
  113.         $ref = (string) $ws->dimension['ref'];
  114.         if( strpos$ref':') !== false ) {
  115.             $d explode(':'$ref);
  116.             if(!isset($d[1]))
  117.                 return array(0,0);
  118.             $index $this->_columnIndex$d[1] );
  119.             return array( $index[0]+1$index[1]+1);
  120.         } else if ( strlen$ref ) ) { // 0.6.8
  121.             $index $this->_columnIndex$ref );
  122.             return array( $index[0]+1$index[1]+1);
  123.         } else
  124.             return array(0,0);
  125.     }
  126.     // sheets numeration: 1,2,3....
  127.     function rows$worksheet_id ) {
  128.         if (($ws $this->worksheet$worksheet_id)) === false)
  129.             return false;
  130.         $rows = array();
  131.         $curR 0;
  132.         list($cols,) = $this->dimension$worksheet_id );
  133.         foreach ($ws->sheetData->row as $row) {
  134. //            echo 'row<br />';
  135.             foreach ($row->as $c) {
  136.                 list($curC,) = $this->_columnIndex((string) $c['r']);
  137.                 $rows$curR ][ $curC ] = $this->value($c);
  138.             }
  139.             for ($i 0$i $cols$i++)
  140.                 if (!isset($rows[$curR][$i]))
  141.                     $rows$curR ][ $i ] = '';
  142.             ksort$rows$curR ] );
  143.             $curR++;
  144.         }
  145.         return $rows;
  146.     }
  147.     function rowsEx$worksheet_id ) {
  148.         if (($ws $this->worksheet$worksheet_id)) === false)
  149.             return false;
  150.         $rows = array();
  151.         $curR 0;
  152.         list($cols,) = $this->dimension$worksheet_id );
  153.         foreach ($ws->sheetData->row as $row) {
  154.             foreach ($row->as $c) {
  155.                 list($curC,) = $this->_columnIndex((string) $c['r']);
  156.                 $t = (string)$c['t'];
  157.                 $s = (int)$c['s'];
  158.                 if ($s && isset($this->workbook_cell_formats$s ])) {
  159.                     $format $this->workbook_cell_formats$s ]['format'];
  160.                     if ( strpos($format,'m') !== false )
  161.                         $t 'd';
  162.                 } else
  163.                     $format '';
  164.                 $rows$curR ][ $curC ] = array(
  165.                     'type' => $t,
  166.                     'name' => (string) $c['r'],
  167.                     'value' => $this->value($c),
  168.                     'href' => $this->href$c ),
  169.                     'f' => (string) $c->f,
  170.                     'format' => $format
  171.                 );
  172.             }
  173.             for ($i 0$i $cols$i++) {
  174.                 if ( !isset($rows[$curR][$i]) ) {
  175.                     // 0.6.8
  176.                     for ($c ''$j $i$j >= 0$j intval($j 26) - 1)
  177.                         $c chr$j 26 65 ) . $c;
  178.                     $rows$curR ][$i] = array(
  179.                         'type' => '',
  180. //                        'name' => chr($i + 65).($curR+1),
  181.                         'name' => $c.($curR+1),
  182.                         'value' => '',
  183.                         'href' => '',
  184.                         'f' => '',
  185.                         'format' => ''
  186.                     );
  187.                 }
  188.             }
  189.             ksort$rows$curR ] );
  190.             $curR++;
  191.         }
  192.         return $rows;
  193.     }
  194.     // thx Gonzo
  195.     private function _columnIndex$cell 'A1' ) {
  196.         if (preg_match("/([A-Z]+)(\d+)/"$cell$matches)) {
  197.             $col $matches[1];
  198.             $row $matches[2];
  199.             $colLen strlen($col);
  200.             $index 0;
  201.             for ($i $colLen-1$i >= 0$i--)
  202.                 $index += (ord($col{$i}) - 64) * pow(26$colLen-$i-1);
  203.             return array($index-1$row-1);
  204.         } else
  205.             throw new Exception("Invalid cell index.");
  206.     }
  207.     function value$cell ) {
  208.         // Determine data type
  209.         $dataType = (string) $cell['t'];
  210.         switch ($dataType) {
  211.             case "s":
  212.                 // Value is a shared string
  213.                 if ((string)$cell->!= '') {
  214.                     $value $this->sharedstrings[intval($cell->v)];
  215.                 } else {
  216.                     $value '';
  217.                 }
  218.                 break;
  219.             case "b":
  220.                 // Value is boolean
  221.                 $value = (string)$cell->v;
  222.                 if ($value == '0') {
  223.                     $value false;
  224.                 } else if ($value == '1') {
  225.                     $value true;
  226.                 } else {
  227.                     $value = (bool)$cell->v;
  228.                 }
  229.                 break;
  230.             case "inlineStr":
  231.                 // Value is rich text inline
  232.                 $value $this->_parseRichText($cell->is);
  233.                 break;
  234.             case "e":
  235.                 // Value is an error message
  236.                 if ((string)$cell->!= '') {
  237.                     $value = (string)$cell->v;
  238.                 } else {
  239.                     $value '';
  240.                 }
  241.                 break;
  242.             default:
  243.                 // Value is a string
  244.                 $value = (string)$cell->v;
  245.                 // Check for numeric values
  246.                 if (is_numeric($value) && $dataType != 's') {
  247.                     if ($value == (int)$value$value = (int)$value;
  248.                     elseif ($value == (float)$value$value = (float)$value;
  249.                 }
  250.         }
  251.         return $value;
  252.     }
  253.     function href$cell ) {
  254.         return isset( $this->hyperlinks[ (string) $cell['r'] ] ) ? $this->hyperlinks[ (string) $cell['r'] ] : '';
  255.     }
  256.     function styles() {
  257.         return $this->styles;
  258.     }
  259.     function _unzip$filename$is_data false ) {
  260.         // Clear current file
  261.         $this->datasec = array();
  262.         if ($is_data) {
  263.             $this->package['filename'] = 'default.xlsx';
  264.             $this->package['mtime'] = time();
  265.             $this->package['size'] = strlen$filename );
  266.             $vZ $filename;
  267.         } else {
  268.             if (!is_readable($filename)) {
  269.                 $this->error'File not found' );
  270.                 return false;
  271.             }
  272.             // Package information
  273.             $this->package['filename'] = $filename;
  274.             $this->package['mtime'] = filemtime$filename );
  275.             $this->package['size'] = filesize$filename );
  276.             // Read file
  277.             $oF fopen($filename'rb');
  278.             $vZ fread($oF$this->package['size']);
  279.             fclose($oF);
  280.         }
  281.         // Cut end of central directory
  282. /*        $aE = explode("\x50\x4b\x05\x06", $vZ);
  283.         if (count($aE) == 1) {
  284.             $this->error('Unknown format');
  285.             return false;
  286.         }
  287. */
  288.         if ( ($pcd strrpos$vZ"\x50\x4b\x05\x06" )) === false ) {
  289.             $this->error('Unknown format');
  290.             return false;
  291.         }
  292.         $aE = array(
  293.             => substr$vZ0$pcd ),
  294.             => substr$vZ$pcd )
  295.         );
  296.         // Normal way
  297.         $aP unpack('x16/v1CL'$aE[1]);
  298.         $this->package['comment'] = substr($aE[1], 18$aP['CL']);
  299.         // Translates end of line from other operating systems
  300.         $this->package['comment'] = strtr($this->package['comment'], array("\r\n" => "\n""\r" => "\n"));
  301.         // Cut the entries from the central directory
  302.         $aE explode("\x50\x4b\x01\x02"$vZ);
  303.         // Explode to each part
  304.         $aE explode("\x50\x4b\x03\x04"$aE[0]);
  305.         // Shift out spanning signature or empty entry
  306.         array_shift($aE);
  307.         // Loop through the entries
  308.         foreach ($aE as $vZ) {
  309.             $aI = array();
  310.             $aI['E']  = 0;
  311.             $aI['EM'] = '';
  312.             // Retrieving local file header information
  313. //            $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
  314.             $aP unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL'$vZ);
  315.             // Check if data is encrypted
  316. //            $bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE;
  317.             $bE false;
  318.             $nF $aP['FNL'];
  319.             $mF $aP['EFL'];
  320.             // Special case : value block after the compressed data
  321.             if ($aP['GPF'] & 0x0008) {
  322.                 $aP1 unpack('V1CRC/V1CS/V1UCS'substr($vZ, -12));
  323.                 $aP['CRC'] = $aP1['CRC'];
  324.                 $aP['CS']  = $aP1['CS'];
  325.                 $aP['UCS'] = $aP1['UCS'];
  326.                 // 2013-08-10
  327.                 $vZ substr($vZ0, -12);
  328.                 if (substr($vZ,-4) === "\x50\x4b\x07\x08")
  329.                     $vZ substr($vZ0, -4);
  330.             }
  331.             // Getting stored filename
  332.             $aI['N'] = substr($vZ26$nF);
  333.             if (substr($aI['N'], -1) == '/') {
  334.                 // is a directory entry - will be skipped
  335.                 continue;
  336.             }
  337.             // Truncate full filename in path and filename
  338.             $aI['P'] = dirname($aI['N']);
  339.             $aI['P'] = $aI['P'] == '.' '' $aI['P'];
  340.             $aI['N'] = basename($aI['N']);
  341.             $vZ substr($vZ26 $nF $mF);
  342.             if ( strlen($vZ) != $aP['CS'] ) { // check only if availabled
  343.               $aI['E']  = 1;
  344.               $aI['EM'] = 'Compressed size is not equal with the value in header information.';
  345.             } else {
  346.                 if ($bE) {
  347.                     $aI['E']  = 5;
  348.                     $aI['EM'] = 'File is encrypted, which is not supported from this class.';
  349.                 } else {
  350.                     switch($aP['CM']) {
  351.                         case 0// Stored
  352.                             // Here is nothing to do, the file ist flat.
  353.                             break;
  354.                         case 8// Deflated
  355.                             $vZ gzinflate($vZ);
  356.                             break;
  357.                         case 12// BZIP2
  358.                             if (! extension_loaded('bz2')) {
  359.                                 if (strtoupper(substr(PHP_OS03)) == 'WIN') {
  360.                                   @dl('php_bz2.dll');
  361.                                 } else {
  362.                                   @dl('bz2.so');
  363.                                 }
  364.                             }
  365.                             if (extension_loaded('bz2')) {
  366.                                 $vZ bzdecompress($vZ);
  367.                             } else {
  368.                                 $aI['E']  = 7;
  369.                                 $aI['EM'] = "PHP BZIP2 extension not available.";
  370.                             }
  371.                             break;
  372.                         default:
  373.                           $aI['E']  = 6;
  374.                           $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
  375.                     }
  376.                     if (! $aI['E']) {
  377.                         if ($vZ === FALSE) {
  378.                             $aI['E']  = 2;
  379.                             $aI['EM'] = 'Decompression of data failed.';
  380.                         } else {
  381.                             if (strlen($vZ) != $aP['UCS']) {
  382.                                 $aI['E']  = 3;
  383.                                 $aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
  384.                             } else {
  385.                                 if (crc32($vZ) != $aP['CRC']) {
  386.                                     $aI['E']  = 4;
  387.                                     $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
  388.                                 }
  389.                             }
  390.                         }
  391.                     }
  392.                 }
  393.             }
  394.             $aI['D'] = $vZ;
  395.             // DOS to UNIX timestamp
  396.             $aI['T'] = mktime(($aP['FT']  & 0xf800) >> 11,
  397.                               ($aP['FT']  & 0x07e0) >>  5,
  398.                               ($aP['FT']  & 0x001f) <<  1,
  399.                               ($aP['FD']  & 0x01e0) >>  5,
  400.                               ($aP['FD']  & 0x001f),
  401.                               (($aP['FD'] & 0xfe00) >>  9) + 1980);
  402.             //$this->Entries[] = &new SimpleUnzipEntry($aI);
  403.             $this->package['entries'][] = array(
  404.                 'data' => $aI['D'],
  405.                 'error' => $aI['E'],
  406.                 'error_msg' => $aI['EM'],
  407.                 'name' => $aI['N'],
  408.                 'path' => $aI['P'],
  409.                 'time' => $aI['T']
  410.             );
  411.         } // end for each entries
  412.     }
  413.     function getPackage() {
  414.         return $this->package;
  415.     }
  416.     function entryExists$name ) { // 0.6.6
  417.         $dir dirname$name );
  418.         $name basename$name );
  419.         foreach( $this->package['entries'] as $entry)
  420.             if ( $entry['path'] == $dir && $entry['name'] == $name)
  421.                 return true;
  422.         return false;
  423.     }
  424.     function getEntryData$name ) {
  425.         $dir dirname$name );
  426.         $name basename$name );
  427.         foreach( $this->package['entries'] as $entry)
  428.             if ( $entry['path'] == $dir && $entry['name'] == $name)
  429.                 return $entry['data'];
  430.         $this->error('Unknown format');
  431.         return false;
  432.     }
  433.     function getEntryXML$name ) {
  434.         if ( ($entry_xml $this->getEntryData$name ))
  435.             &&  ($entry_xmlobj simplexml_load_string$entry_xml )))
  436.             return $entry_xmlobj;
  437.         $this->error('Entry not found: '.$name );
  438.         return false;
  439.     }
  440.     function unixstamp$excelDateTime ) {
  441.         $d floor$excelDateTime ); // seconds since 1900
  442.         $t $excelDateTime $d;
  443.         return ($d 0) ? ( $d 25569 ) * 86400 $t 86400 $t 86400;
  444.     }
  445.     function error$set false ) {
  446.         if ($set) {
  447.             $this->error $set;
  448.             if ($this->debug)
  449.                 trigger_error__CLASS__.': '.$setE_USER_WARNING );
  450.         } else {
  451.             return $this->error;
  452.         }
  453.     }
  454.     function success() {
  455.         return !$this->error;
  456.     }
  457.     function _parse() {
  458.         // Document data holders
  459.         $this->sharedstrings = array();
  460.         $this->sheets = array();
  461. //        $this->styles = array();
  462.         // Read relations and search for officeDocument
  463.         if ( $relations $this->getEntryXML("_rels/.rels" ) ) {
  464.             foreach ($relations->Relationship as $rel) {
  465.                 if ($rel["Type"] == SimpleXLSX::SCHEMA_REL_OFFICEDOCUMENT) {
  466. //                        echo 'workbook found<br />';
  467.                     // Found office document! Read workbook & relations...
  468.                     // Workbook
  469.                     if ( $this->workbook $this->getEntryXML$rel['Target'] )) {
  470. //                        echo 'workbook read<br />';
  471.                         if ( $workbookRelations $this->getEntryXMLdirname($rel['Target']) . '/_rels/workbook.xml.rels' )) {
  472. //                            echo 'workbook relations<br />';
  473.                             // Loop relations for workbook and extract sheets...
  474.                             foreach ($workbookRelations->Relationship as $workbookRelation) {
  475.                                 $path dirname($rel['Target']) . '/' $workbookRelation['Target'];
  476.                                 if ($workbookRelation['Type'] == SimpleXLSX::SCHEMA_REL_WORKSHEET) { // Sheets
  477. //                                    echo 'sheet<br />';
  478.                                     if ( $sheet $this->getEntryXML$path ) ) {
  479.                                         $this->sheetsstr_replace'rId''', (string) $workbookRelation['Id']) ] = $sheet;
  480. //                                        echo '<pre>'.htmlspecialchars( print_r( $sheet, true ) ).'</pre>';
  481.                                     }
  482.                                 } else if ($workbookRelation['Type'] == SimpleXLSX::SCHEMA_REL_SHAREDSTRINGS && $this->entryExists$path )) { // 0.6.6
  483. //                                    echo 'sharedstrings<br />';
  484.                                     if ( $sharedStrings $this->getEntryXML$path ) ) {
  485.                                         foreach ($sharedStrings->si as $val) {
  486.                                             if (isset($val->t)) {
  487.                                                 $this->sharedstrings[] = (string)$val->t;
  488.                                             } elseif (isset($val->r)) {
  489.                                                 $this->sharedstrings[] = $this->_parseRichText($val);
  490.                                             }
  491.                                         }
  492.                                     }
  493.                                 } else if ($workbookRelation['Type'] == SimpleXLSX::SCHEMA_REL_STYLES) {
  494.                                     $this->styles $this->getEntryXML$path );
  495.                                     $nf = array();
  496.                                     if ( $this->styles->numFmts->numFmt != NULL )
  497.                                         foreach( $this->styles->numFmts->numFmt as $v )
  498.                                             $nf[ (int) $v['numFmtId'] ] = (string) $v['formatCode'];
  499.                                     if ( $this->styles->cellXfs->xf != NULL )
  500.                                         foreach( $this->styles->cellXfs->xf as $v ) {
  501.                                             $v = (array) $v->attributes();
  502.                                             $v $v['@attributes'];
  503.                                             if (isset($this->built_in_cell_formats$v['numFmtId'] ]) )
  504.                                                 $v['format'] = $this->built_in_cell_formats$v['numFmtId'] ];
  505.                                             else if (isset($nf$v['numFmtId'] ]))
  506.                                                 $v['format'] = $nf$v['numFmtId'] ];
  507.                                             else
  508.                                                 $v['format'] = '';
  509.                                             $this->workbook_cell_formats[] = $v;
  510.                                         }
  511. //                                    print_r( $this->workbook_cell_formats );
  512.                                 }
  513.                             }
  514.                             break;
  515.                         }
  516.                     }
  517.                 }
  518.             }
  519.         }
  520.         // Sort sheets
  521.         ksort($this->sheets);
  522.     }
  523.     private function _parseRichText($is null) {
  524.         $value = array();
  525.         if (isset($is->t)) {
  526.             $value[] = (string)$is->t;
  527.         } else {
  528.             foreach ($is->as $run) {
  529.                 $value[] = (string)$run->t;
  530.             }
  531.         }
  532.         return implode(' '$value);
  533.     }
  534. }
  535. ?>