ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
Excel5.php
Go to the documentation of this file.
1 <?php
28 // Original file header of ParseXL (used as the base for this class):
29 // --------------------------------------------------------------------------------
30 // Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
31 // trex005, and mmp11 (SourceForge.net)
32 // http://sourceforge.net/projects/phpexcelreader/
33 // Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
34 // Modelled moreso after Perl Excel Parse/Write modules
35 // Added Parse_Excel_Spreadsheet object
36 // Reads a whole worksheet or tab as row,column array or as
37 // associated hash of indexed rows and named column fields
38 // Added variables for worksheet (tab) indexes and names
39 // Added an object call for loading individual woorksheets
40 // Changed default indexing defaults to 0 based arrays
41 // Fixed date/time and percent formats
42 // Includes patches found at SourceForge...
43 // unicode patch by nobody
44 // unpack("d") machine depedency patch by matchy
45 // boundsheet utf16 patch by bjaenichen
46 // Renamed functions for shorter names
47 // General code cleanup and rigor, including <80 column width
48 // Included a testcase Excel file and PHP example calls
49 // Code works for PHP 5.x
50 
51 // Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
52 // http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
53 // Decoding of formula conditions, results, and tokens.
54 // Support for user-defined named cells added as an array "namedcells"
55 // Patch code for user-defined named cells supports single cells only.
56 // NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
57 // external sheet reference structure
58 
59 
61 if (!defined('PHPEXCEL_ROOT')) {
65  define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
66  require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
67 }
68 
79 {
80  // ParseXL definitions
81  const XLS_BIFF8 = 0x0600;
82  const XLS_BIFF7 = 0x0500;
83  const XLS_WorkbookGlobals = 0x0005;
84  const XLS_Worksheet = 0x0010;
85 
86  // record identifiers
87  const XLS_Type_FORMULA = 0x0006;
88  const XLS_Type_EOF = 0x000a;
89  const XLS_Type_PROTECT = 0x0012;
90  const XLS_Type_OBJECTPROTECT = 0x0063;
91  const XLS_Type_SCENPROTECT = 0x00dd;
92  const XLS_Type_PASSWORD = 0x0013;
93  const XLS_Type_HEADER = 0x0014;
94  const XLS_Type_FOOTER = 0x0015;
95  const XLS_Type_EXTERNSHEET = 0x0017;
96  const XLS_Type_DEFINEDNAME = 0x0018;
99  const XLS_Type_NOTE = 0x001c;
100  const XLS_Type_SELECTION = 0x001d;
101  const XLS_Type_DATEMODE = 0x0022;
102  const XLS_Type_EXTERNNAME = 0x0023;
103  const XLS_Type_LEFTMARGIN = 0x0026;
104  const XLS_Type_RIGHTMARGIN = 0x0027;
105  const XLS_Type_TOPMARGIN = 0x0028;
106  const XLS_Type_BOTTOMMARGIN = 0x0029;
107  const XLS_Type_PRINTGRIDLINES = 0x002b;
108  const XLS_Type_FILEPASS = 0x002f;
109  const XLS_Type_FONT = 0x0031;
110  const XLS_Type_CONTINUE = 0x003c;
111  const XLS_Type_PANE = 0x0041;
112  const XLS_Type_CODEPAGE = 0x0042;
113  const XLS_Type_DEFCOLWIDTH = 0x0055;
114  const XLS_Type_OBJ = 0x005d;
115  const XLS_Type_COLINFO = 0x007d;
116  const XLS_Type_IMDATA = 0x007f;
117  const XLS_Type_SHEETPR = 0x0081;
118  const XLS_Type_HCENTER = 0x0083;
119  const XLS_Type_VCENTER = 0x0084;
120  const XLS_Type_SHEET = 0x0085;
121  const XLS_Type_PALETTE = 0x0092;
122  const XLS_Type_SCL = 0x00a0;
123  const XLS_Type_PAGESETUP = 0x00a1;
124  const XLS_Type_MULRK = 0x00bd;
125  const XLS_Type_MULBLANK = 0x00be;
126  const XLS_Type_DBCELL = 0x00d7;
127  const XLS_Type_XF = 0x00e0;
128  const XLS_Type_MERGEDCELLS = 0x00e5;
129  const XLS_Type_MSODRAWINGGROUP = 0x00eb;
130  const XLS_Type_MSODRAWING = 0x00ec;
131  const XLS_Type_SST = 0x00fc;
132  const XLS_Type_LABELSST = 0x00fd;
133  const XLS_Type_EXTSST = 0x00ff;
134  const XLS_Type_EXTERNALBOOK = 0x01ae;
135  const XLS_Type_DATAVALIDATIONS = 0x01b2;
136  const XLS_Type_TXO = 0x01b6;
137  const XLS_Type_HYPERLINK = 0x01b8;
138  const XLS_Type_DATAVALIDATION = 0x01be;
139  const XLS_Type_DIMENSION = 0x0200;
140  const XLS_Type_BLANK = 0x0201;
141  const XLS_Type_NUMBER = 0x0203;
142  const XLS_Type_LABEL = 0x0204;
143  const XLS_Type_BOOLERR = 0x0205;
144  const XLS_Type_STRING = 0x0207;
145  const XLS_Type_ROW = 0x0208;
146  const XLS_Type_INDEX = 0x020b;
147  const XLS_Type_ARRAY = 0x0221;
149  const XLS_Type_WINDOW2 = 0x023e;
150  const XLS_Type_RK = 0x027e;
151  const XLS_Type_STYLE = 0x0293;
152  const XLS_Type_FORMAT = 0x041e;
153  const XLS_Type_SHAREDFMLA = 0x04bc;
154  const XLS_Type_BOF = 0x0809;
155  const XLS_Type_SHEETPROTECTION = 0x0867;
156  const XLS_Type_RANGEPROTECTION = 0x0868;
157  const XLS_Type_SHEETLAYOUT = 0x0862;
158  const XLS_Type_XFEXT = 0x087d;
159  const XLS_Type_UNKNOWN = 0xffff;
160 
168  private $_readDataOnly = false;
169 
176  private $_loadSheetsOnly = null;
177 
183  private $_readFilter = null;
184 
191 
198 
205 
211  private $_data;
212 
218  private $_dataSize;
219 
225  private $_pos;
226 
232  private $_phpExcel;
233 
239  private $_phpSheet;
240 
246  private $_version;
247 
254  private $_codepage;
255 
261  private $_formats;
262 
268  private $_objFonts;
269 
275  private $_palette;
276 
282  private $_sheets;
283 
290 
296  private $_ref;
297 
304 
310  private $_definedname;
311 
317  private $_sst;
318 
324  private $_frozen;
325 
331  private $_isFitToPages;
332 
338  private $_objs;
339 
345  private $_textObjects;
346 
352  private $_cellNotes;
353 
360 
366  private $_drawingData;
367 
373  private $_xfIndex;
374 
381 
388 
395 
403 
404 
412  public function getReadDataOnly()
413  {
414  return $this->_readDataOnly;
415  }
416 
426  public function setReadDataOnly($pValue = false)
427  {
428  $this->_readDataOnly = $pValue;
429  return $this;
430  }
431 
439  public function getLoadSheetsOnly()
440  {
441  return $this->_loadSheetsOnly;
442  }
443 
453  public function setLoadSheetsOnly($value = null)
454  {
455  $this->_loadSheetsOnly = is_array($value) ?
456  $value : array($value);
457  return $this;
458  }
459 
466  public function setLoadAllSheets()
467  {
468  $this->_loadSheetsOnly = null;
469  return $this;
470  }
471 
477  public function getReadFilter() {
478  return $this->_readFilter;
479  }
480 
487  public function setReadFilter(PHPExcel_Reader_IReadFilter $pValue) {
488  $this->_readFilter = $pValue;
489  return $this;
490  }
491 
495  public function __construct() {
496  $this->_readFilter = new PHPExcel_Reader_DefaultReadFilter();
497  }
498 
505  public function canRead($pFilename)
506  {
507  // Check if file exists
508  if (!file_exists($pFilename)) {
509  throw new Exception("Could not open " . $pFilename . " for reading! File does not exist.");
510  }
511 
512  try {
513  // Use ParseXL for the hard work.
514  $ole = new PHPExcel_Shared_OLERead();
515 
516  // get excel data
517  $res = $ole->read($pFilename);
518  return true;
519 
520  } catch (Exception $e) {
521  return false;
522  }
523  }
524 
531  public function listWorksheetNames($pFilename)
532  {
533  // Check if file exists
534  if (!file_exists($pFilename)) {
535  throw new Exception("Could not open " . $pFilename . " for reading! File does not exist.");
536  }
537 
538  $worksheetNames = array();
539 
540  // Read the OLE file
541  $this->_loadOLE($pFilename);
542 
543  // total byte size of Excel data (workbook global substream + sheet substreams)
544  $this->_dataSize = strlen($this->_data);
545 
546  $this->_pos = 0;
547  $this->_sheets = array();
548 
549  // Parse Workbook Global Substream
550  while ($this->_pos < $this->_dataSize) {
551  $code = self::_GetInt2d($this->_data, $this->_pos);
552 
553  switch ($code) {
554  case self::XLS_Type_BOF: $this->_readBof(); break;
555  case self::XLS_Type_SHEET: $this->_readSheet(); break;
556  case self::XLS_Type_EOF: $this->_readDefault(); break 2;
557  default: $this->_readDefault(); break;
558  }
559  }
560 
561  foreach ($this->_sheets as $sheet) {
562  if ($sheet['sheetType'] != 0x00) {
563  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
564  continue;
565  }
566 
567  $worksheetNames[] = $sheet['name'];
568  }
569 
570  return $worksheetNames;
571  }
572 
573 
581  public function load($pFilename)
582  {
583  // Read the OLE file
584  $this->_loadOLE($pFilename);
585 
586  // Initialisations
587  $this->_phpExcel = new PHPExcel;
588  $this->_phpExcel->removeSheetByIndex(0); // remove 1st sheet
589  if (!$this->_readDataOnly) {
590  $this->_phpExcel->removeCellStyleXfByIndex(0); // remove the default style
591  $this->_phpExcel->removeCellXfByIndex(0); // remove the default style
592  }
593 
594  // Read the summary information stream (containing meta data)
595  $this->_readSummaryInformation();
596 
597  // Read the Additional document summary information stream (containing application-specific meta data)
599 
600  // total byte size of Excel data (workbook global substream + sheet substreams)
601  $this->_dataSize = strlen($this->_data);
602 
603  // initialize
604  $this->_pos = 0;
605  $this->_codepage = 'CP1252';
606  $this->_formats = array();
607  $this->_objFonts = array();
608  $this->_palette = array();
609  $this->_sheets = array();
610  $this->_externalBooks = array();
611  $this->_ref = array();
612  $this->_definedname = array();
613  $this->_sst = array();
614  $this->_drawingGroupData = '';
615  $this->_xfIndex = '';
616  $this->_mapCellXfIndex = array();
617  $this->_mapCellStyleXfIndex = array();
618 
619  // Parse Workbook Global Substream
620  while ($this->_pos < $this->_dataSize) {
621  $code = self::_GetInt2d($this->_data, $this->_pos);
622 
623  switch ($code) {
624  case self::XLS_Type_BOF: $this->_readBof(); break;
625  case self::XLS_Type_FILEPASS: $this->_readFilepass(); break;
626  case self::XLS_Type_CODEPAGE: $this->_readCodepage(); break;
627  case self::XLS_Type_DATEMODE: $this->_readDateMode(); break;
628  case self::XLS_Type_FONT: $this->_readFont(); break;
629  case self::XLS_Type_FORMAT: $this->_readFormat(); break;
630  case self::XLS_Type_XF: $this->_readXf(); break;
631  case self::XLS_Type_XFEXT: $this->_readXfExt(); break;
632  case self::XLS_Type_STYLE: $this->_readStyle(); break;
633  case self::XLS_Type_PALETTE: $this->_readPalette(); break;
634  case self::XLS_Type_SHEET: $this->_readSheet(); break;
635  case self::XLS_Type_EXTERNALBOOK: $this->_readExternalBook(); break;
636  case self::XLS_Type_EXTERNNAME: $this->_readExternName(); break;
637  case self::XLS_Type_EXTERNSHEET: $this->_readExternSheet(); break;
638  case self::XLS_Type_DEFINEDNAME: $this->_readDefinedName(); break;
639  case self::XLS_Type_MSODRAWINGGROUP: $this->_readMsoDrawingGroup(); break;
640  case self::XLS_Type_SST: $this->_readSst(); break;
641  case self::XLS_Type_EOF: $this->_readDefault(); break 2;
642  default: $this->_readDefault(); break;
643  }
644  }
645 
646  // Resolve indexed colors for font, fill, and border colors
647  // Cannot be resolved already in XF record, because PALETTE record comes afterwards
648  if (!$this->_readDataOnly) {
649  foreach ($this->_objFonts as $objFont) {
650  if (isset($objFont->colorIndex)) {
651  $color = self::_readColor($objFont->colorIndex,$this->_palette,$this->_version);
652  $objFont->getColor()->setRGB($color['rgb']);
653  }
654  }
655 
656  foreach ($this->_phpExcel->getCellXfCollection() as $objStyle) {
657  // fill start and end color
658  $fill = $objStyle->getFill();
659 
660  if (isset($fill->startcolorIndex)) {
661  $startColor = self::_readColor($fill->startcolorIndex,$this->_palette,$this->_version);
662  $fill->getStartColor()->setRGB($startColor['rgb']);
663  }
664 
665  if (isset($fill->endcolorIndex)) {
666  $endColor = self::_readColor($fill->endcolorIndex,$this->_palette,$this->_version);
667  $fill->getEndColor()->setRGB($endColor['rgb']);
668  }
669 
670  // border colors
671  $top = $objStyle->getBorders()->getTop();
672  $right = $objStyle->getBorders()->getRight();
673  $bottom = $objStyle->getBorders()->getBottom();
674  $left = $objStyle->getBorders()->getLeft();
675  $diagonal = $objStyle->getBorders()->getDiagonal();
676 
677  if (isset($top->colorIndex)) {
678  $borderTopColor = self::_readColor($top->colorIndex,$this->_palette,$this->_version);
679  $top->getColor()->setRGB($borderTopColor['rgb']);
680  }
681 
682  if (isset($right->colorIndex)) {
683  $borderRightColor = self::_readColor($right->colorIndex,$this->_palette,$this->_version);
684  $right->getColor()->setRGB($borderRightColor['rgb']);
685  }
686 
687  if (isset($bottom->colorIndex)) {
688  $borderBottomColor = self::_readColor($bottom->colorIndex,$this->_palette,$this->_version);
689  $bottom->getColor()->setRGB($borderBottomColor['rgb']);
690  }
691 
692  if (isset($left->colorIndex)) {
693  $borderLeftColor = self::_readColor($left->colorIndex,$this->_palette,$this->_version);
694  $left->getColor()->setRGB($borderLeftColor['rgb']);
695  }
696 
697  if (isset($diagonal->colorIndex)) {
698  $borderDiagonalColor = self::_readColor($diagonal->colorIndex,$this->_palette,$this->_version);
699  $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
700  }
701  }
702  }
703 
704  // treat MSODRAWINGGROUP records, workbook-level Escher
705  if (!$this->_readDataOnly && $this->_drawingGroupData) {
706  $escherWorkbook = new PHPExcel_Shared_Escher();
707  $reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook);
708  $escherWorkbook = $reader->load($this->_drawingGroupData);
709 
710  // debug Escher stream
711  //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
712  //$debug->load($this->_drawingGroupData);
713  }
714 
715  // Parse the individual sheets
716  foreach ($this->_sheets as $sheet) {
717 
718  if ($sheet['sheetType'] != 0x00) {
719  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
720  continue;
721  }
722 
723  // check if sheet should be skipped
724  if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) {
725  continue;
726  }
727 
728  // add sheet to PHPExcel object
729  $this->_phpSheet = $this->_phpExcel->createSheet();
730  $this->_phpSheet->setTitle($sheet['name']);
731  $this->_phpSheet->setSheetState($sheet['sheetState']);
732 
733  $this->_pos = $sheet['offset'];
734 
735  // Initialize isFitToPages. May change after reading SHEETPR record.
736  $this->_isFitToPages = false;
737 
738  // Initialize drawingData
739  $this->_drawingData = '';
740 
741  // Initialize objs
742  $this->_objs = array();
743 
744  // Initialize shared formula parts
745  $this->_sharedFormulaParts = array();
746 
747  // Initialize shared formulas
748  $this->_sharedFormulas = array();
749 
750  // Initialize text objs
751  $this->_textObjects = array();
752 
753  // Initialize cell annotations
754  $this->_cellNotes = array();
755  $this->textObjRef = -1;
756 
757  while ($this->_pos <= $this->_dataSize - 4) {
758  $code = self::_GetInt2d($this->_data, $this->_pos);
759 
760  switch ($code) {
761  case self::XLS_Type_BOF: $this->_readBof(); break;
762  case self::XLS_Type_PRINTGRIDLINES: $this->_readPrintGridlines(); break;
763  case self::XLS_Type_DEFAULTROWHEIGHT: $this->_readDefaultRowHeight(); break;
764  case self::XLS_Type_SHEETPR: $this->_readSheetPr(); break;
765  case self::XLS_Type_HORIZONTALPAGEBREAKS: $this->_readHorizontalPageBreaks(); break;
766  case self::XLS_Type_VERTICALPAGEBREAKS: $this->_readVerticalPageBreaks(); break;
767  case self::XLS_Type_HEADER: $this->_readHeader(); break;
768  case self::XLS_Type_FOOTER: $this->_readFooter(); break;
769  case self::XLS_Type_HCENTER: $this->_readHcenter(); break;
770  case self::XLS_Type_VCENTER: $this->_readVcenter(); break;
771  case self::XLS_Type_LEFTMARGIN: $this->_readLeftMargin(); break;
772  case self::XLS_Type_RIGHTMARGIN: $this->_readRightMargin(); break;
773  case self::XLS_Type_TOPMARGIN: $this->_readTopMargin(); break;
774  case self::XLS_Type_BOTTOMMARGIN: $this->_readBottomMargin(); break;
775  case self::XLS_Type_PAGESETUP: $this->_readPageSetup(); break;
776  case self::XLS_Type_PROTECT: $this->_readProtect(); break;
777  case self::XLS_Type_SCENPROTECT: $this->_readScenProtect(); break;
778  case self::XLS_Type_OBJECTPROTECT: $this->_readObjectProtect(); break;
779  case self::XLS_Type_PASSWORD: $this->_readPassword(); break;
780  case self::XLS_Type_DEFCOLWIDTH: $this->_readDefColWidth(); break;
781  case self::XLS_Type_COLINFO: $this->_readColInfo(); break;
782  case self::XLS_Type_DIMENSION: $this->_readDefault(); break;
783  case self::XLS_Type_ROW: $this->_readRow(); break;
784  case self::XLS_Type_DBCELL: $this->_readDefault(); break;
785  case self::XLS_Type_RK: $this->_readRk(); break;
786  case self::XLS_Type_LABELSST: $this->_readLabelSst(); break;
787  case self::XLS_Type_MULRK: $this->_readMulRk(); break;
788  case self::XLS_Type_NUMBER: $this->_readNumber(); break;
789  case self::XLS_Type_FORMULA: $this->_readFormula(); break;
790  case self::XLS_Type_SHAREDFMLA: $this->_readSharedFmla(); break;
791  case self::XLS_Type_BOOLERR: $this->_readBoolErr(); break;
792  case self::XLS_Type_MULBLANK: $this->_readMulBlank(); break;
793  case self::XLS_Type_LABEL: $this->_readLabel(); break;
794  case self::XLS_Type_BLANK: $this->_readBlank(); break;
795  case self::XLS_Type_MSODRAWING: $this->_readMsoDrawing(); break;
796  case self::XLS_Type_OBJ: $this->_readObj(); break;
797  case self::XLS_Type_WINDOW2: $this->_readWindow2(); break;
798  case self::XLS_Type_SCL: $this->_readScl(); break;
799  case self::XLS_Type_PANE: $this->_readPane(); break;
800  case self::XLS_Type_SELECTION: $this->_readSelection(); break;
801  case self::XLS_Type_MERGEDCELLS: $this->_readMergedCells(); break;
802  case self::XLS_Type_HYPERLINK: $this->_readHyperLink(); break;
803  case self::XLS_Type_DATAVALIDATIONS: $this->_readDataValidations(); break;
804  case self::XLS_Type_DATAVALIDATION: $this->_readDataValidation(); break;
805  case self::XLS_Type_SHEETLAYOUT: $this->_readSheetLayout(); break;
806  case self::XLS_Type_SHEETPROTECTION: $this->_readSheetProtection(); break;
807  case self::XLS_Type_RANGEPROTECTION: $this->_readRangeProtection(); break;
808  case self::XLS_Type_NOTE: $this->_readNote(); break;
809  //case self::XLS_Type_IMDATA: $this->_readImData(); break;
810  case self::XLS_Type_TXO: $this->_readTextObject(); break;
811  case self::XLS_Type_CONTINUE: $this->_readContinue(); break;
812  case self::XLS_Type_EOF: $this->_readDefault(); break 2;
813  default: $this->_readDefault(); break;
814  }
815 
816  }
817 
818  // treat MSODRAWING records, sheet-level Escher
819  if (!$this->_readDataOnly && $this->_drawingData) {
820  $escherWorksheet = new PHPExcel_Shared_Escher();
821  $reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet);
822  $escherWorksheet = $reader->load($this->_drawingData);
823 
824  // debug Escher stream
825  //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
826  //$debug->load($this->_drawingData);
827 
828  // get all spContainers in one long array, so they can be mapped to OBJ records
829  $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
830  }
831 
832  // treat OBJ records
833  foreach ($this->_objs as $n => $obj) {
834 // echo '<hr /><b>Object</b> reference is ',$n,'<br />';
835 // var_dump($obj);
836 // echo '<br />';
837 
838  // the first shape container never has a corresponding OBJ record, hence $n + 1
839  $spContainer = $allSpContainers[$n + 1];
840 
841  // we skip all spContainers that are a part of a group shape since we cannot yet handle those
842  if ($spContainer->getNestingLevel() > 1) {
843  continue;
844  }
845 
846  // calculate the width and height of the shape
847  list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($spContainer->getStartCoordinates());
848  list($endColumn, $endRow) = PHPExcel_Cell::coordinateFromString($spContainer->getEndCoordinates());
849 
850  $startOffsetX = $spContainer->getStartOffsetX();
851  $startOffsetY = $spContainer->getStartOffsetY();
852  $endOffsetX = $spContainer->getEndOffsetX();
853  $endOffsetY = $spContainer->getEndOffsetY();
854 
855  $width = PHPExcel_Shared_Excel5::getDistanceX($this->_phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
856  $height = PHPExcel_Shared_Excel5::getDistanceY($this->_phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
857 
858  // calculate offsetX and offsetY of the shape
859  $offsetX = $startOffsetX * PHPExcel_Shared_Excel5::sizeCol($this->_phpSheet, $startColumn) / 1024;
860  $offsetY = $startOffsetY * PHPExcel_Shared_Excel5::sizeRow($this->_phpSheet, $startRow) / 256;
861 
862  switch ($obj['otObjType']) {
863 
864  case 0x19:
865  // Note
866 // echo 'Cell Annotation Object<br />';
867 // echo 'Object ID is ',$obj['idObjID'],'<br />';
868 //
869  if (isset($this->_cellNotes[$obj['idObjID']])) {
870  $cellNote = $this->_cellNotes[$obj['idObjID']];
871 
872 // echo '_cellNotes[',$obj['idObjID'],']: ';
873 // var_dump($cellNote);
874 // echo '<br />';
875 //
876  if (isset($this->_textObjects[$obj['idObjID']])) {
877  $textObject = $this->_textObjects[$obj['idObjID']];
878 // echo '_textObject: ';
879 // var_dump($textObject);
880 // echo '<br />';
881 //
882  $this->_cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
883  $text = $textObject['text'];
884  }
885 // echo $text,'<br />';
886  }
887  break;
888 
889  case 0x08:
890 // echo 'Picture Object<br />';
891  // picture
892 
893  // get index to BSE entry (1-based)
894  $BSEindex = $spContainer->getOPT(0x0104);
895  $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
896  $BSE = $BSECollection[$BSEindex - 1];
897  $blipType = $BSE->getBlipType();
898 
899  // need check because some blip types are not supported by Escher reader such as EMF
900  if ($blip = $BSE->getBlip()) {
901  $ih = imagecreatefromstring($blip->getData());
902  $drawing = new PHPExcel_Worksheet_MemoryDrawing();
903  $drawing->setImageResource($ih);
904 
905  // width, height, offsetX, offsetY
906  $drawing->setResizeProportional(false);
907  $drawing->setWidth($width);
908  $drawing->setHeight($height);
909  $drawing->setOffsetX($offsetX);
910  $drawing->setOffsetY($offsetY);
911 
912  switch ($blipType) {
914  $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG);
916  break;
917 
919  $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG);
920  $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG);
921  break;
922  }
923 
924  $drawing->setWorksheet($this->_phpSheet);
925  $drawing->setCoordinates($spContainer->getStartCoordinates());
926  }
927 
928  break;
929 
930  default:
931  // other object type
932  break;
933 
934  }
935  }
936 
937  // treat SHAREDFMLA records
938  if ($this->_version == self::XLS_BIFF8) {
939  foreach ($this->_sharedFormulaParts as $cell => $baseCell) {
940  list($column, $row) = PHPExcel_Cell::coordinateFromString($cell);
941  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle()) ) {
942  $formula = $this->_getFormulaFromStructure($this->_sharedFormulas[$baseCell], $cell);
943  $this->_phpSheet->getCell($cell)->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
944  }
945  }
946  }
947 
948  if (count($this->_cellNotes) > 0) {
949  foreach($this->_cellNotes as $note => $noteDetails) {
950 // echo '<b>Cell annotation ',$note,'</b><br />';
951 // var_dump($noteDetails);
952 // echo '<br />';
953  $cellAddress = str_replace('$','',$noteDetails['cellRef']);
954  $this->_phpSheet->getComment( $cellAddress )
955  ->setAuthor( $noteDetails['author'] )
956  ->setText($this->_parseRichText($noteDetails['objTextData']['text']) );
957  }
958  }
959  }
960 
961  // add the named ranges (defined names)
962  foreach ($this->_definedname as $definedName) {
963  if ($definedName['isBuiltInName']) {
964  switch ($definedName['name']) {
965 
966  case pack('C', 0x06):
967  // print area
968  // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
969 
970  $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
971 
972  $extractedRanges = array();
973  foreach ($ranges as $range) {
974  // $range should look like one of these
975  // Foo!$C$7:$J$66
976  // Bar!$A$1:$IV$2
977 
978  $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
979  $sheetName = $explodes[0];
980 
981  if (count($explodes) == 2) {
982  $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
983  }
984  }
985  if ($docSheet = $this->_phpExcel->getSheetByName($sheetName)) {
986  $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
987  }
988  break;
989 
990  case pack('C', 0x07):
991  // print titles (repeating rows)
992  // Assuming BIFF8, there are 3 cases
993  // 1. repeating rows
994  // formula looks like this: Sheet!$A$1:$IV$2
995  // rows 1-2 repeat
996  // 2. repeating columns
997  // formula looks like this: Sheet!$A$1:$B$65536
998  // columns A-B repeat
999  // 3. both repeating rows and repeating columns
1000  // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
1001 
1002  $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1003 
1004  foreach ($ranges as $range) {
1005  // $range should look like this one of these
1006  // Sheet!$A$1:$B$65536
1007  // Sheet!$A$1:$IV$2
1008 
1009  $explodes = explode('!', $range);
1010 
1011  if (count($explodes) == 2) {
1012  if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
1013 
1014  $extractedRange = $explodes[1];
1015  $extractedRange = str_replace('$', '', $extractedRange);
1016 
1017  $coordinateStrings = explode(':', $extractedRange);
1018  if (count($coordinateStrings) == 2) {
1019  list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]);
1020  list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]);
1021 
1022  if ($firstColumn == 'A' and $lastColumn == 'IV') {
1023  // then we have repeating rows
1024  $docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow));
1025  } elseif ($firstRow == 1 and $lastRow == 65536) {
1026  // then we have repeating columns
1027  $docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn));
1028  }
1029  }
1030  }
1031  }
1032  }
1033  break;
1034 
1035  }
1036  } else {
1037  // Extract range
1038  $explodes = explode('!', $definedName['formula']);
1039 
1040  if (count($explodes) == 2) {
1041  if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
1042  $extractedRange = $explodes[1];
1043  $extractedRange = str_replace('$', '', $extractedRange);
1044 
1045  $localOnly = ($definedName['scope'] == 0) ? false : true;
1046  $scope = ($definedName['scope'] == 0) ?
1047  null : $this->_phpExcel->getSheetByName($this->_sheets[$definedName['scope'] - 1]['name']);
1048 
1049  $this->_phpExcel->addNamedRange( new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, $localOnly, $scope) );
1050  }
1051  }
1052  }
1053  }
1054 
1055  return $this->_phpExcel;
1056  }
1057 
1063  private function _loadOLE($pFilename)
1064  {
1065  // OLE reader
1066  $ole = new PHPExcel_Shared_OLERead();
1067 
1068  // get excel data,
1069  $res = $ole->read($pFilename);
1070  // Get workbook data: workbook stream + sheet streams
1071  $this->_data = $ole->getStream($ole->wrkbook);
1072 
1073  // Get summary information data
1074  $this->_summaryInformation = $ole->getStream($ole->summaryInformation);
1075 
1076  // Get additional document summary information data
1077  $this->_documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1078 
1079  // Get user-defined property data
1080 // $this->_userDefinedProperties = $ole->getUserDefinedProperties();
1081  }
1082 
1086  private function _readSummaryInformation()
1087  {
1088  if (!isset($this->_summaryInformation)) {
1089  return;
1090  }
1091 
1092  // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1093  // offset: 2; size: 2;
1094  // offset: 4; size: 2; OS version
1095  // offset: 6; size: 2; OS indicator
1096  // offset: 8; size: 16
1097  // offset: 24; size: 4; section count
1098  $secCount = self::_GetInt4d($this->_summaryInformation, 24);
1099 
1100  // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
1101  // offset: 44; size: 4
1102  $secOffset = self::_GetInt4d($this->_summaryInformation, 44);
1103 
1104  // section header
1105  // offset: $secOffset; size: 4; section length
1106  $secLength = self::_GetInt4d($this->_summaryInformation, $secOffset);
1107 
1108  // offset: $secOffset+4; size: 4; property count
1109  $countProperties = self::_GetInt4d($this->_summaryInformation, $secOffset+4);
1110 
1111  // initialize code page (used to resolve string values)
1112  $codePage = 'CP1252';
1113 
1114  // offset: ($secOffset+8); size: var
1115  // loop through property decarations and properties
1116  for ($i = 0; $i < $countProperties; ++$i) {
1117 
1118  // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
1119  $id = self::_GetInt4d($this->_summaryInformation, ($secOffset+8) + (8 * $i));
1120 
1121  // Use value of property id as appropriate
1122  // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
1123  $offset = self::_GetInt4d($this->_summaryInformation, ($secOffset+12) + (8 * $i));
1124 
1125  $type = self::_GetInt4d($this->_summaryInformation, $secOffset + $offset);
1126 
1127  // initialize property value
1128  $value = null;
1129 
1130  // extract property value based on property type
1131  switch ($type) {
1132  case 0x02: // 2 byte signed integer
1133  $value = self::_GetInt2d($this->_summaryInformation, $secOffset + 4 + $offset);
1134  break;
1135 
1136  case 0x03: // 4 byte signed integer
1137  $value = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
1138  break;
1139 
1140  case 0x13: // 4 byte unsigned integer
1141  // not needed yet, fix later if necessary
1142  break;
1143 
1144  case 0x1E: // null-terminated string prepended by dword string length
1145  $byteLength = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
1146  $value = substr($this->_summaryInformation, $secOffset + 8 + $offset, $byteLength);
1147  $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1148  $value = rtrim($value);
1149  break;
1150 
1151  case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1152  // PHP-time
1153  $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_summaryInformation, $secOffset + 4 + $offset, 8));
1154  break;
1155 
1156  case 0x47: // Clipboard format
1157  // not needed yet, fix later if necessary
1158  break;
1159  }
1160 
1161  switch ($id) {
1162  case 0x01: // Code Page
1163  $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1164  break;
1165 
1166  case 0x02: // Title
1167  $this->_phpExcel->getProperties()->setTitle($value);
1168  break;
1169 
1170  case 0x03: // Subject
1171  $this->_phpExcel->getProperties()->setSubject($value);
1172  break;
1173 
1174  case 0x04: // Author (Creator)
1175  $this->_phpExcel->getProperties()->setCreator($value);
1176  break;
1177 
1178  case 0x05: // Keywords
1179  $this->_phpExcel->getProperties()->setKeywords($value);
1180  break;
1181 
1182  case 0x06: // Comments (Description)
1183  $this->_phpExcel->getProperties()->setDescription($value);
1184  break;
1185 
1186  case 0x07: // Template
1187  // Not supported by PHPExcel
1188  break;
1189 
1190  case 0x08: // Last Saved By (LastModifiedBy)
1191  $this->_phpExcel->getProperties()->setLastModifiedBy($value);
1192  break;
1193 
1194  case 0x09: // Revision
1195  // Not supported by PHPExcel
1196  break;
1197 
1198  case 0x0A: // Total Editing Time
1199  // Not supported by PHPExcel
1200  break;
1201 
1202  case 0x0B: // Last Printed
1203  // Not supported by PHPExcel
1204  break;
1205 
1206  case 0x0C: // Created Date/Time
1207  $this->_phpExcel->getProperties()->setCreated($value);
1208  break;
1209 
1210  case 0x0D: // Modified Date/Time
1211  $this->_phpExcel->getProperties()->setModified($value);
1212  break;
1213 
1214  case 0x0E: // Number of Pages
1215  // Not supported by PHPExcel
1216  break;
1217 
1218  case 0x0F: // Number of Words
1219  // Not supported by PHPExcel
1220  break;
1221 
1222  case 0x10: // Number of Characters
1223  // Not supported by PHPExcel
1224  break;
1225 
1226  case 0x11: // Thumbnail
1227  // Not supported by PHPExcel
1228  break;
1229 
1230  case 0x12: // Name of creating application
1231  // Not supported by PHPExcel
1232  break;
1233 
1234  case 0x13: // Security
1235  // Not supported by PHPExcel
1236  break;
1237 
1238  }
1239  }
1240  }
1241 
1246  {
1247  if (!isset($this->_documentSummaryInformation)) {
1248  return;
1249  }
1250 
1251  // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1252  // offset: 2; size: 2;
1253  // offset: 4; size: 2; OS version
1254  // offset: 6; size: 2; OS indicator
1255  // offset: 8; size: 16
1256  // offset: 24; size: 4; section count
1257  $secCount = self::_GetInt4d($this->_documentSummaryInformation, 24);
1258 // echo '$secCount = ',$secCount,'<br />';
1259 
1260  // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
1261  // offset: 44; size: 4; first section offset
1262  $secOffset = self::_GetInt4d($this->_documentSummaryInformation, 44);
1263 // echo '$secOffset = ',$secOffset,'<br />';
1264 
1265  // section header
1266  // offset: $secOffset; size: 4; section length
1267  $secLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset);
1268 // echo '$secLength = ',$secLength,'<br />';
1269 
1270  // offset: $secOffset+4; size: 4; property count
1271  $countProperties = self::_GetInt4d($this->_documentSummaryInformation, $secOffset+4);
1272 // echo '$countProperties = ',$countProperties,'<br />';
1273 
1274  // initialize code page (used to resolve string values)
1275  $codePage = 'CP1252';
1276 
1277  // offset: ($secOffset+8); size: var
1278  // loop through property decarations and properties
1279  for ($i = 0; $i < $countProperties; ++$i) {
1280 // echo 'Property ',$i,'<br />';
1281  // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
1282  $id = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+8) + (8 * $i));
1283 // echo 'ID is ',$id,'<br />';
1284 
1285  // Use value of property id as appropriate
1286  // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48)
1287  $offset = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+12) + (8 * $i));
1288 
1289  $type = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + $offset);
1290 // echo 'Type is ',$type,', ';
1291 
1292  // initialize property value
1293  $value = null;
1294 
1295  // extract property value based on property type
1296  switch ($type) {
1297  case 0x02: // 2 byte signed integer
1298  $value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1299  break;
1300 
1301  case 0x03: // 4 byte signed integer
1302  $value = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1303  break;
1304 
1305  case 0x13: // 4 byte unsigned integer
1306  // not needed yet, fix later if necessary
1307  break;
1308 
1309  case 0x1E: // null-terminated string prepended by dword string length
1310  $byteLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1311  $value = substr($this->_documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1312  $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1313  $value = rtrim($value);
1314  break;
1315 
1316  case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1317  // PHP-Time
1318  $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_documentSummaryInformation, $secOffset + 4 + $offset, 8));
1319  break;
1320 
1321  case 0x47: // Clipboard format
1322  // not needed yet, fix later if necessary
1323  break;
1324  }
1325 
1326  switch ($id) {
1327  case 0x01: // Code Page
1328  $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1329  break;
1330 
1331  case 0x02: // Category
1332  $this->_phpExcel->getProperties()->setCategory($value);
1333  break;
1334 
1335  case 0x03: // Presentation Target
1336  // Not supported by PHPExcel
1337  break;
1338 
1339  case 0x04: // Bytes
1340  // Not supported by PHPExcel
1341  break;
1342 
1343  case 0x05: // Lines
1344  // Not supported by PHPExcel
1345  break;
1346 
1347  case 0x06: // Paragraphs
1348  // Not supported by PHPExcel
1349  break;
1350 
1351  case 0x07: // Slides
1352  // Not supported by PHPExcel
1353  break;
1354 
1355  case 0x08: // Notes
1356  // Not supported by PHPExcel
1357  break;
1358 
1359  case 0x09: // Hidden Slides
1360  // Not supported by PHPExcel
1361  break;
1362 
1363  case 0x0A: // MM Clips
1364  // Not supported by PHPExcel
1365  break;
1366 
1367  case 0x0B: // Scale Crop
1368  // Not supported by PHPExcel
1369  break;
1370 
1371  case 0x0C: // Heading Pairs
1372  // Not supported by PHPExcel
1373  break;
1374 
1375  case 0x0D: // Titles of Parts
1376  // Not supported by PHPExcel
1377  break;
1378 
1379  case 0x0E: // Manager
1380  $this->_phpExcel->getProperties()->setManager($value);
1381  break;
1382 
1383  case 0x0F: // Company
1384  $this->_phpExcel->getProperties()->setCompany($value);
1385  break;
1386 
1387  case 0x10: // Links up-to-date
1388  // Not supported by PHPExcel
1389  break;
1390 
1391  }
1392  }
1393  }
1394 
1398  private function _readDefault()
1399  {
1400  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1401 // $recordData = substr($this->_data, $this->_pos + 4, $length);
1402 
1403  // move stream pointer to next record
1404  $this->_pos += 4 + $length;
1405  }
1406 
1407 
1412  private function _readNote()
1413  {
1414 // echo '<b>Read Cell Annotation</b><br />';
1415  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1416  $recordData = substr($this->_data, $this->_pos + 4, $length);
1417 
1418  // move stream pointer to next record
1419  $this->_pos += 4 + $length;
1420 
1421  if ($this->_readDataOnly) {
1422  return;
1423  }
1424 
1425  $cellAddress = $this->_readBIFF8CellAddress(substr($recordData, 0, 4));
1426  if ($this->_version == self::XLS_BIFF8) {
1427  $noteObjID = self::_GetInt2d($recordData, 6);
1428  $noteAuthor = self::_readUnicodeStringLong(substr($recordData, 8));
1429  $noteAuthor = $noteAuthor['value'];
1430 // echo 'Note Address=',$cellAddress,'<br />';
1431 // echo 'Note Object ID=',$noteObjID,'<br />';
1432 // echo 'Note Author=',$noteAuthor,'<hr />';
1433 //
1434  $this->_cellNotes[$noteObjID] = array('cellRef' => $cellAddress,
1435  'objectID' => $noteObjID,
1436  'author' => $noteAuthor
1437  );
1438  } else {
1439  $extension = false;
1440  if ($cellAddress == '$B$65536') {
1441  // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1442  // note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1443  // max 2048 bytes will probably throw a wobbly.
1444  $row = self::_GetInt2d($recordData, 0);
1445  $extension = true;
1446  $cellAddress = array_pop(array_keys($this->_phpSheet->getComments()));
1447  }
1448 // echo 'Note Address=',$cellAddress,'<br />';
1449 
1450  $cellAddress = str_replace('$','',$cellAddress);
1451  $noteLength = self::_GetInt2d($recordData, 4);
1452  $noteText = trim(substr($recordData, 6));
1453 // echo 'Note Length=',$noteLength,'<br />';
1454 // echo 'Note Text=',$noteText,'<br />';
1455 
1456  if ($extension) {
1457  // Concatenate this extension with the currently set comment for the cell
1458  $comment = $this->_phpSheet->getComment( $cellAddress );
1459  $commentText = $comment->getText()->getPlainText();
1460  $comment->setText($this->_parseRichText($commentText.$noteText) );
1461  } else {
1462  // Set comment for the cell
1463  $this->_phpSheet->getComment( $cellAddress )
1464 // ->setAuthor( $author )
1465  ->setText($this->_parseRichText($noteText) );
1466  }
1467  }
1468 
1469  }
1470 
1474  private function _readTextObject()
1475  {
1476  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1477  $recordData = substr($this->_data, $this->_pos + 4, $length);
1478 
1479  // move stream pointer to next record
1480  $this->_pos += 4 + $length;
1481 
1482  if ($this->_readDataOnly) {
1483  return;
1484  }
1485 
1486  // recordData consists of an array of subrecords looking like this:
1487  // grbit: 2 bytes; Option Flags
1488  // rot: 2 bytes; rotation
1489  // cchText: 2 bytes; length of the text (in the first continue record)
1490  // cbRuns: 2 bytes; length of the formatting (in the second continue record)
1491  // followed by the continuation records containing the actual text and formatting
1492  $grbitOpts = self::_GetInt2d($recordData, 0);
1493  $rot = self::_GetInt2d($recordData, 2);
1494  $cchText = self::_GetInt2d($recordData, 10);
1495  $cbRuns = self::_GetInt2d($recordData, 12);
1496  $text = $this->_getSplicedRecordData();
1497 
1498  $this->_textObjects[$this->textObjRef] = array(
1499  'text' => substr($text["recordData"],$text["spliceOffsets"][0]+1,$cchText),
1500  'format' => substr($text["recordData"],$text["spliceOffsets"][1],$cbRuns),
1501  'alignment' => $grbitOpts,
1502  'rotation' => $rot
1503  );
1504 
1505 // echo '<b>_readTextObject()</b><br />';
1506 // var_dump($this->_textObjects[$this->textObjRef]);
1507 // echo '<br />';
1508  }
1509 
1513  private function _readBof()
1514  {
1515  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1516  $recordData = substr($this->_data, $this->_pos + 4, $length);
1517 
1518  // move stream pointer to next record
1519  $this->_pos += 4 + $length;
1520 
1521  // offset: 2; size: 2; type of the following data
1522  $substreamType = self::_GetInt2d($recordData, 2);
1523 
1524  switch ($substreamType) {
1525  case self::XLS_WorkbookGlobals:
1526  $version = self::_GetInt2d($recordData, 0);
1527  if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1528  throw new Exception('Cannot read this Excel file. Version is too old.');
1529  }
1530  $this->_version = $version;
1531  break;
1532 
1533  case self::XLS_Worksheet:
1534  // do not use this version information for anything
1535  // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1536  break;
1537 
1538  default:
1539  // substream, e.g. chart
1540  // just skip the entire substream
1541  do {
1542  $code = self::_GetInt2d($this->_data, $this->_pos);
1543  $this->_readDefault();
1544  } while ($code != self::XLS_Type_EOF && $this->_pos < $this->_dataSize);
1545  break;
1546  }
1547  }
1548 
1560  private function _readFilepass()
1561  {
1562  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1563 // $recordData = substr($this->_data, $this->_pos + 4, $length);
1564 
1565  // move stream pointer to next record
1566  $this->_pos += 4 + $length;
1567 
1568  throw new Exception('Cannot read encrypted file');
1569  }
1570 
1580  private function _readCodepage()
1581  {
1582  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1583  $recordData = substr($this->_data, $this->_pos + 4, $length);
1584 
1585  // move stream pointer to next record
1586  $this->_pos += 4 + $length;
1587 
1588  // offset: 0; size: 2; code page identifier
1589  $codepage = self::_GetInt2d($recordData, 0);
1590 
1591  $this->_codepage = PHPExcel_Shared_CodePage::NumberToName($codepage);
1592  }
1593 
1606  private function _readDateMode()
1607  {
1608  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1609  $recordData = substr($this->_data, $this->_pos + 4, $length);
1610 
1611  // move stream pointer to next record
1612  $this->_pos += 4 + $length;
1613 
1614  // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
1616  if (ord($recordData{0}) == 1) {
1618  }
1619  }
1620 
1624  private function _readFont()
1625  {
1626  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1627  $recordData = substr($this->_data, $this->_pos + 4, $length);
1628 
1629  // move stream pointer to next record
1630  $this->_pos += 4 + $length;
1631 
1632  if (!$this->_readDataOnly) {
1633  $objFont = new PHPExcel_Style_Font();
1634 
1635  // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
1636  $size = self::_GetInt2d($recordData, 0);
1637  $objFont->setSize($size / 20);
1638 
1639  // offset: 2; size: 2; option flags
1640  // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
1641  // bit: 1; mask 0x0002; italic
1642  $isItalic = (0x0002 & self::_GetInt2d($recordData, 2)) >> 1;
1643  if ($isItalic) $objFont->setItalic(true);
1644 
1645  // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
1646  // bit: 3; mask 0x0008; strike
1647  $isStrike = (0x0008 & self::_GetInt2d($recordData, 2)) >> 3;
1648  if ($isStrike) $objFont->setStrikethrough(true);
1649 
1650  // offset: 4; size: 2; colour index
1651  $colorIndex = self::_GetInt2d($recordData, 4);
1652  $objFont->colorIndex = $colorIndex;
1653 
1654  // offset: 6; size: 2; font weight
1655  $weight = self::_GetInt2d($recordData, 6);
1656  switch ($weight) {
1657  case 0x02BC:
1658  $objFont->setBold(true);
1659  break;
1660  }
1661 
1662  // offset: 8; size: 2; escapement type
1663  $escapement = self::_GetInt2d($recordData, 8);
1664  switch ($escapement) {
1665  case 0x0001:
1666  $objFont->setSuperScript(true);
1667  break;
1668  case 0x0002:
1669  $objFont->setSubScript(true);
1670  break;
1671  }
1672 
1673  // offset: 10; size: 1; underline type
1674  $underlineType = ord($recordData{10});
1675  switch ($underlineType) {
1676  case 0x00:
1677  break; // no underline
1678  case 0x01:
1679  $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE);
1680  break;
1681  case 0x02:
1682  $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLE);
1683  break;
1684  case 0x21:
1685  $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLEACCOUNTING);
1686  break;
1687  case 0x22:
1688  $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLEACCOUNTING);
1689  break;
1690  }
1691 
1692  // offset: 11; size: 1; font family
1693  // offset: 12; size: 1; character set
1694  // offset: 13; size: 1; not used
1695  // offset: 14; size: var; font name
1696  if ($this->_version == self::XLS_BIFF8) {
1697  $string = self::_readUnicodeStringShort(substr($recordData, 14));
1698  } else {
1699  $string = $this->_readByteStringShort(substr($recordData, 14));
1700  }
1701  $objFont->setName($string['value']);
1702 
1703  $this->_objFonts[] = $objFont;
1704  }
1705  }
1706 
1721  private function _readFormat()
1722  {
1723  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1724  $recordData = substr($this->_data, $this->_pos + 4, $length);
1725 
1726  // move stream pointer to next record
1727  $this->_pos += 4 + $length;
1728 
1729  if (!$this->_readDataOnly) {
1730  $indexCode = self::_GetInt2d($recordData, 0);
1731 
1732  if ($this->_version == self::XLS_BIFF8) {
1733  $string = self::_readUnicodeStringLong(substr($recordData, 2));
1734  } else {
1735  // BIFF7
1736  $string = $this->_readByteStringShort(substr($recordData, 2));
1737  }
1738 
1739  $formatString = $string['value'];
1740  $this->_formats[$indexCode] = $formatString;
1741  }
1742  }
1743 
1758  private function _readXf()
1759  {
1760  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1761  $recordData = substr($this->_data, $this->_pos + 4, $length);
1762 
1763  // move stream pointer to next record
1764  $this->_pos += 4 + $length;
1765 
1766  $objStyle = new PHPExcel_Style();
1767 
1768  if (!$this->_readDataOnly) {
1769  // offset: 0; size: 2; Index to FONT record
1770  if (self::_GetInt2d($recordData, 0) < 4) {
1771  $fontIndex = self::_GetInt2d($recordData, 0);
1772  } else {
1773  // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
1774  // check the OpenOffice documentation of the FONT record
1775  $fontIndex = self::_GetInt2d($recordData, 0) - 1;
1776  }
1777  $objStyle->setFont($this->_objFonts[$fontIndex]);
1778 
1779  // offset: 2; size: 2; Index to FORMAT record
1780  $numberFormatIndex = self::_GetInt2d($recordData, 2);
1781  if (isset($this->_formats[$numberFormatIndex])) {
1782  // then we have user-defined format code
1783  $numberformat = array('code' => $this->_formats[$numberFormatIndex]);
1784  } elseif (($code = PHPExcel_Style_NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
1785  // then we have built-in format code
1786  $numberformat = array('code' => $code);
1787  } else {
1788  // we set the general format code
1789  $numberformat = array('code' => 'General');
1790  }
1791  $objStyle->getNumberFormat()->setFormatCode($numberformat['code']);
1792 
1793  // offset: 4; size: 2; XF type, cell protection, and parent style XF
1794  // bit 2-0; mask 0x0007; XF_TYPE_PROT
1795  $xfTypeProt = self::_GetInt2d($recordData, 4);
1796  // bit 0; mask 0x01; 1 = cell is locked
1797  $isLocked = (0x01 & $xfTypeProt) >> 0;
1798  $objStyle->getProtection()->setLocked($isLocked ?
1800 
1801  // bit 1; mask 0x02; 1 = Formula is hidden
1802  $isHidden = (0x02 & $xfTypeProt) >> 1;
1803  $objStyle->getProtection()->setHidden($isHidden ?
1804  PHPExcel_Style_Protection::PROTECTION_PROTECTED : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED);
1805 
1806  // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
1807  $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
1808 
1809  // offset: 6; size: 1; Alignment and text break
1810  // bit 2-0, mask 0x07; horizontal alignment
1811  $horAlign = (0x07 & ord($recordData{6})) >> 0;
1812  switch ($horAlign) {
1813  case 0:
1814  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_GENERAL);
1815  break;
1816  case 1:
1817  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
1818  break;
1819  case 2:
1820  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
1821  break;
1822  case 3:
1823  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
1824  break;
1825  case 5:
1826  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
1827  break;
1828  case 6:
1829  $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER_CONTINUOUS);
1830  break;
1831  }
1832  // bit 3, mask 0x08; wrap text
1833  $wrapText = (0x08 & ord($recordData{6})) >> 3;
1834  switch ($wrapText) {
1835  case 0:
1836  $objStyle->getAlignment()->setWrapText(false);
1837  break;
1838  case 1:
1839  $objStyle->getAlignment()->setWrapText(true);
1840  break;
1841  }
1842  // bit 6-4, mask 0x70; vertical alignment
1843  $vertAlign = (0x70 & ord($recordData{6})) >> 4;
1844  switch ($vertAlign) {
1845  case 0:
1846  $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_TOP);
1847  break;
1848  case 1:
1849  $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER);
1850  break;
1851  case 2:
1852  $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_BOTTOM);
1853  break;
1854  case 3:
1855  $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_JUSTIFY);
1856  break;
1857  }
1858 
1859  if ($this->_version == self::XLS_BIFF8) {
1860  // offset: 7; size: 1; XF_ROTATION: Text rotation angle
1861  $angle = ord($recordData{7});
1862  $rotation = 0;
1863  if ($angle <= 90) {
1864  $rotation = $angle;
1865  } else if ($angle <= 180) {
1866  $rotation = 90 - $angle;
1867  } else if ($angle == 255) {
1868  $rotation = -165;
1869  }
1870  $objStyle->getAlignment()->setTextRotation($rotation);
1871 
1872  // offset: 8; size: 1; Indentation, shrink to cell size, and text direction
1873  // bit: 3-0; mask: 0x0F; indent level
1874  $indent = (0x0F & ord($recordData{8})) >> 0;
1875  $objStyle->getAlignment()->setIndent($indent);
1876 
1877  // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
1878  $shrinkToFit = (0x10 & ord($recordData{8})) >> 4;
1879  switch ($shrinkToFit) {
1880  case 0:
1881  $objStyle->getAlignment()->setShrinkToFit(false);
1882  break;
1883  case 1:
1884  $objStyle->getAlignment()->setShrinkToFit(true);
1885  break;
1886  }
1887 
1888  // offset: 9; size: 1; Flags used for attribute groups
1889 
1890  // offset: 10; size: 4; Cell border lines and background area
1891  // bit: 3-0; mask: 0x0000000F; left style
1892  if ($bordersLeftStyle = self::_mapBorderStyle((0x0000000F & self::_GetInt4d($recordData, 10)) >> 0)) {
1893  $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
1894  }
1895  // bit: 7-4; mask: 0x000000F0; right style
1896  if ($bordersRightStyle = self::_mapBorderStyle((0x000000F0 & self::_GetInt4d($recordData, 10)) >> 4)) {
1897  $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
1898  }
1899  // bit: 11-8; mask: 0x00000F00; top style
1900  if ($bordersTopStyle = self::_mapBorderStyle((0x00000F00 & self::_GetInt4d($recordData, 10)) >> 8)) {
1901  $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
1902  }
1903  // bit: 15-12; mask: 0x0000F000; bottom style
1904  if ($bordersBottomStyle = self::_mapBorderStyle((0x0000F000 & self::_GetInt4d($recordData, 10)) >> 12)) {
1905  $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
1906  }
1907  // bit: 22-16; mask: 0x007F0000; left color
1908  $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::_GetInt4d($recordData, 10)) >> 16;
1909 
1910  // bit: 29-23; mask: 0x3F800000; right color
1911  $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::_GetInt4d($recordData, 10)) >> 23;
1912 
1913  // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
1914  $diagonalDown = (0x40000000 & self::_GetInt4d($recordData, 10)) >> 30 ?
1915  true : false;
1916 
1917  // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
1918  $diagonalUp = (0x80000000 & self::_GetInt4d($recordData, 10)) >> 31 ?
1919  true : false;
1920 
1921  if ($diagonalUp == false && $diagonalDown == false) {
1922  $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_NONE);
1923  } elseif ($diagonalUp == true && $diagonalDown == false) {
1924  $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_UP);
1925  } elseif ($diagonalUp == false && $diagonalDown == true) {
1926  $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_DOWN);
1927  } elseif ($diagonalUp == true && $diagonalDown == true) {
1928  $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_BOTH);
1929  }
1930 
1931  // offset: 14; size: 4;
1932  // bit: 6-0; mask: 0x0000007F; top color
1933  $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::_GetInt4d($recordData, 14)) >> 0;
1934 
1935  // bit: 13-7; mask: 0x00003F80; bottom color
1936  $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::_GetInt4d($recordData, 14)) >> 7;
1937 
1938  // bit: 20-14; mask: 0x001FC000; diagonal color
1939  $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::_GetInt4d($recordData, 14)) >> 14;
1940 
1941  // bit: 24-21; mask: 0x01E00000; diagonal style
1942  if ($bordersDiagonalStyle = self::_mapBorderStyle((0x01E00000 & self::_GetInt4d($recordData, 14)) >> 21)) {
1943  $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
1944  }
1945 
1946  // bit: 31-26; mask: 0xFC000000 fill pattern
1947  if ($fillType = self::_mapFillPattern((0xFC000000 & self::_GetInt4d($recordData, 14)) >> 26)) {
1948  $objStyle->getFill()->setFillType($fillType);
1949  }
1950  // offset: 18; size: 2; pattern and background colour
1951  // bit: 6-0; mask: 0x007F; color index for pattern color
1952  $objStyle->getFill()->startcolorIndex = (0x007F & self::_GetInt2d($recordData, 18)) >> 0;
1953 
1954  // bit: 13-7; mask: 0x3F80; color index for pattern background
1955  $objStyle->getFill()->endcolorIndex = (0x3F80 & self::_GetInt2d($recordData, 18)) >> 7;
1956  } else {
1957  // BIFF5
1958 
1959  // offset: 7; size: 1; Text orientation and flags
1960  $orientationAndFlags = ord($recordData{7});
1961 
1962  // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
1963  $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
1964  switch ($xfOrientation) {
1965  case 0:
1966  $objStyle->getAlignment()->setTextRotation(0);
1967  break;
1968  case 1:
1969  $objStyle->getAlignment()->setTextRotation(-165);
1970  break;
1971  case 2:
1972  $objStyle->getAlignment()->setTextRotation(90);
1973  break;
1974  case 3:
1975  $objStyle->getAlignment()->setTextRotation(-90);
1976  break;
1977  }
1978 
1979  // offset: 8; size: 4; cell border lines and background area
1980  $borderAndBackground = self::_GetInt4d($recordData, 8);
1981 
1982  // bit: 6-0; mask: 0x0000007F; color index for pattern color
1983  $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
1984 
1985  // bit: 13-7; mask: 0x00003F80; color index for pattern background
1986  $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
1987 
1988  // bit: 21-16; mask: 0x003F0000; fill pattern
1989  $objStyle->getFill()->setFillType(self::_mapFillPattern((0x003F0000 & $borderAndBackground) >> 16));
1990 
1991  // bit: 24-22; mask: 0x01C00000; bottom line style
1992  $objStyle->getBorders()->getBottom()->setBorderStyle(self::_mapBorderStyle((0x01C00000 & $borderAndBackground) >> 22));
1993 
1994  // bit: 31-25; mask: 0xFE000000; bottom line color
1995  $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
1996 
1997  // offset: 12; size: 4; cell border lines
1998  $borderLines = self::_GetInt4d($recordData, 12);
1999 
2000  // bit: 2-0; mask: 0x00000007; top line style
2001  $objStyle->getBorders()->getTop()->setBorderStyle(self::_mapBorderStyle((0x00000007 & $borderLines) >> 0));
2002 
2003  // bit: 5-3; mask: 0x00000038; left line style
2004  $objStyle->getBorders()->getLeft()->setBorderStyle(self::_mapBorderStyle((0x00000038 & $borderLines) >> 3));
2005 
2006  // bit: 8-6; mask: 0x000001C0; right line style
2007  $objStyle->getBorders()->getRight()->setBorderStyle(self::_mapBorderStyle((0x000001C0 & $borderLines) >> 6));
2008 
2009  // bit: 15-9; mask: 0x0000FE00; top line color index
2010  $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2011 
2012  // bit: 22-16; mask: 0x007F0000; left line color index
2013  $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2014 
2015  // bit: 29-23; mask: 0x3F800000; right line color index
2016  $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2017  }
2018 
2019  // add cellStyleXf or cellXf and update mapping
2020  if ($isCellStyleXf) {
2021  // we only read one style XF record which is always the first
2022  if ($this->_xfIndex == 0) {
2023  $this->_phpExcel->addCellStyleXf($objStyle);
2024  $this->_mapCellStyleXfIndex[$this->_xfIndex] = 0;
2025  }
2026  } else {
2027  // we read all cell XF records
2028  $this->_phpExcel->addCellXf($objStyle);
2029  $this->_mapCellXfIndex[$this->_xfIndex] = count($this->_phpExcel->getCellXfCollection()) - 1;
2030  }
2031 
2032  // update XF index for when we read next record
2033  ++$this->_xfIndex;
2034  }
2035  }
2036 
2040  private function _readXfExt()
2041  {
2042  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2043  $recordData = substr($this->_data, $this->_pos + 4, $length);
2044 
2045  // move stream pointer to next record
2046  $this->_pos += 4 + $length;
2047 
2048  if (!$this->_readDataOnly) {
2049  // offset: 0; size: 2; 0x087D = repeated header
2050 
2051  // offset: 2; size: 2
2052 
2053  // offset: 4; size: 8; not used
2054 
2055  // offset: 12; size: 2; record version
2056 
2057  // offset: 14; size: 2; index to XF record which this record modifies
2058  $ixfe = self::_GetInt2d($recordData, 14);
2059 
2060  // offset: 16; size: 2; not used
2061 
2062  // offset: 18; size: 2; number of extension properties that follow
2063  $cexts = self::_GetInt2d($recordData, 18);
2064 
2065  // start reading the actual extension data
2066  $offset = 20;
2067  while ($offset < $length) {
2068  // extension type
2069  $extType = self::_GetInt2d($recordData, $offset);
2070 
2071  // extension length
2072  $cb = self::_GetInt2d($recordData, $offset + 2);
2073 
2074  // extension data
2075  $extData = substr($recordData, $offset + 4, $cb);
2076 
2077  switch ($extType) {
2078  case 4: // fill start color
2079  $xclfType = self::_GetInt2d($extData, 0); // color type
2080  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2081 
2082  if ($xclfType == 2) {
2083  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2084 
2085  // modify the relevant style property
2086  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2087  $fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill();
2088  $fill->getStartColor()->setRGB($rgb);
2089  unset($fill->startcolorIndex); // normal color index does not apply, discard
2090  }
2091  }
2092  break;
2093 
2094  case 5: // fill end color
2095  $xclfType = self::_GetInt2d($extData, 0); // color type
2096  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2097 
2098  if ($xclfType == 2) {
2099  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2100 
2101  // modify the relevant style property
2102  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2103  $fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill();
2104  $fill->getEndColor()->setRGB($rgb);
2105  unset($fill->endcolorIndex); // normal color index does not apply, discard
2106  }
2107  }
2108  break;
2109 
2110  case 7: // border color top
2111  $xclfType = self::_GetInt2d($extData, 0); // color type
2112  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2113 
2114  if ($xclfType == 2) {
2115  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2116 
2117  // modify the relevant style property
2118  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2119  $top = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getTop();
2120  $top->getColor()->setRGB($rgb);
2121  unset($top->colorIndex); // normal color index does not apply, discard
2122  }
2123  }
2124  break;
2125 
2126  case 8: // border color bottom
2127  $xclfType = self::_GetInt2d($extData, 0); // color type
2128  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2129 
2130  if ($xclfType == 2) {
2131  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2132 
2133  // modify the relevant style property
2134  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2135  $bottom = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2136  $bottom->getColor()->setRGB($rgb);
2137  unset($bottom->colorIndex); // normal color index does not apply, discard
2138  }
2139  }
2140  break;
2141 
2142  case 9: // border color left
2143  $xclfType = self::_GetInt2d($extData, 0); // color type
2144  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2145 
2146  if ($xclfType == 2) {
2147  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2148 
2149  // modify the relevant style property
2150  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2151  $left = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2152  $left->getColor()->setRGB($rgb);
2153  unset($left->colorIndex); // normal color index does not apply, discard
2154  }
2155  }
2156  break;
2157 
2158  case 10: // border color right
2159  $xclfType = self::_GetInt2d($extData, 0); // color type
2160  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2161 
2162  if ($xclfType == 2) {
2163  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2164 
2165  // modify the relevant style property
2166  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2167  $right = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getRight();
2168  $right->getColor()->setRGB($rgb);
2169  unset($right->colorIndex); // normal color index does not apply, discard
2170  }
2171  }
2172  break;
2173 
2174  case 11: // border color diagonal
2175  $xclfType = self::_GetInt2d($extData, 0); // color type
2176  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2177 
2178  if ($xclfType == 2) {
2179  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2180 
2181  // modify the relevant style property
2182  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2183  $diagonal = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2184  $diagonal->getColor()->setRGB($rgb);
2185  unset($diagonal->colorIndex); // normal color index does not apply, discard
2186  }
2187  }
2188  break;
2189 
2190  case 13: // font color
2191  $xclfType = self::_GetInt2d($extData, 0); // color type
2192  $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2193 
2194  if ($xclfType == 2) {
2195  $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2196 
2197  // modify the relevant style property
2198  if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2199  $font = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFont();
2200  $font->getColor()->setRGB($rgb);
2201  unset($font->colorIndex); // normal color index does not apply, discard
2202  }
2203  }
2204  break;
2205  }
2206 
2207  $offset += $cb;
2208  }
2209  }
2210 
2211  }
2212 
2216  private function _readStyle()
2217  {
2218  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2219  $recordData = substr($this->_data, $this->_pos + 4, $length);
2220 
2221  // move stream pointer to next record
2222  $this->_pos += 4 + $length;
2223 
2224  if (!$this->_readDataOnly) {
2225  // offset: 0; size: 2; index to XF record and flag for built-in style
2226  $ixfe = self::_GetInt2d($recordData, 0);
2227 
2228  // bit: 11-0; mask 0x0FFF; index to XF record
2229  $xfIndex = (0x0FFF & $ixfe) >> 0;
2230 
2231  // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2232  $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2233 
2234  if ($isBuiltIn) {
2235  // offset: 2; size: 1; identifier for built-in style
2236  $builtInId = ord($recordData{2});
2237 
2238  switch ($builtInId) {
2239  case 0x00:
2240  // currently, we are not using this for anything
2241  break;
2242 
2243  default:
2244  break;
2245  }
2246 
2247  } else {
2248  // user-defined; not supported by PHPExcel
2249  }
2250  }
2251  }
2252 
2256  private function _readPalette()
2257  {
2258  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2259  $recordData = substr($this->_data, $this->_pos + 4, $length);
2260 
2261  // move stream pointer to next record
2262  $this->_pos += 4 + $length;
2263 
2264  if (!$this->_readDataOnly) {
2265  // offset: 0; size: 2; number of following colors
2266  $nm = self::_GetInt2d($recordData, 0);
2267 
2268  // list of RGB colors
2269  for ($i = 0; $i < $nm; ++$i) {
2270  $rgb = substr($recordData, 2 + 4 * $i, 4);
2271  $this->_palette[] = self::_readRGB($rgb);
2272  }
2273  }
2274  }
2275 
2288  private function _readSheet()
2289  {
2290  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2291  $recordData = substr($this->_data, $this->_pos + 4, $length);
2292 
2293  // move stream pointer to next record
2294  $this->_pos += 4 + $length;
2295 
2296  // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2297  $rec_offset = self::_GetInt4d($recordData, 0);
2298 
2299  // offset: 4; size: 1; sheet state
2300  switch (ord($recordData{4})) {
2301  case 0x00: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE; break;
2302  case 0x01: $sheetState = PHPExcel_Worksheet::SHEETSTATE_HIDDEN; break;
2303  case 0x02: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VERYHIDDEN; break;
2304  default: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE; break;
2305  }
2306 
2307  // offset: 5; size: 1; sheet type
2308  $sheetType = ord($recordData{5});
2309 
2310  // offset: 6; size: var; sheet name
2311  if ($this->_version == self::XLS_BIFF8) {
2312  $string = self::_readUnicodeStringShort(substr($recordData, 6));
2313  $rec_name = $string['value'];
2314  } elseif ($this->_version == self::XLS_BIFF7) {
2315  $string = $this->_readByteStringShort(substr($recordData, 6));
2316  $rec_name = $string['value'];
2317  }
2318 
2319  $this->_sheets[] = array(
2320  'name' => $rec_name,
2321  'offset' => $rec_offset,
2322  'sheetState' => $sheetState,
2323  'sheetType' => $sheetType,
2324  );
2325  }
2326 
2330  private function _readExternalBook()
2331  {
2332  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2333  $recordData = substr($this->_data, $this->_pos + 4, $length);
2334 
2335  // move stream pointer to next record
2336  $this->_pos += 4 + $length;
2337 
2338  // offset within record data
2339  $offset = 0;
2340 
2341  // there are 4 types of records
2342  if (strlen($recordData) > 4) {
2343  // external reference
2344  // offset: 0; size: 2; number of sheet names ($nm)
2345  $nm = self::_GetInt2d($recordData, 0);
2346  $offset += 2;
2347 
2348  // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2349  $encodedUrlString = self::_readUnicodeStringLong(substr($recordData, 2));
2350  $offset += $encodedUrlString['size'];
2351 
2352  // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2353  $externalSheetNames = array();
2354  for ($i = 0; $i < $nm; ++$i) {
2355  $externalSheetNameString = self::_readUnicodeStringLong(substr($recordData, $offset));
2356  $externalSheetNames[] = $externalSheetNameString['value'];
2357  $offset += $externalSheetNameString['size'];
2358  }
2359 
2360  // store the record data
2361  $this->_externalBooks[] = array(
2362  'type' => 'external',
2363  'encodedUrl' => $encodedUrlString['value'],
2364  'externalSheetNames' => $externalSheetNames,
2365  );
2366 
2367  } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2368  // internal reference
2369  // offset: 0; size: 2; number of sheet in this document
2370  // offset: 2; size: 2; 0x01 0x04
2371  $this->_externalBooks[] = array(
2372  'type' => 'internal',
2373  );
2374  } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2375  // add-in function
2376  // offset: 0; size: 2; 0x0001
2377  $this->_externalBooks[] = array(
2378  'type' => 'addInFunction',
2379  );
2380  } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2381  // DDE links, OLE links
2382  // offset: 0; size: 2; 0x0000
2383  // offset: 2; size: var; encoded source document name
2384  $this->_externalBooks[] = array(
2385  'type' => 'DDEorOLE',
2386  );
2387  }
2388  }
2389 
2393  private function _readExternName()
2394  {
2395  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2396  $recordData = substr($this->_data, $this->_pos + 4, $length);
2397 
2398  // move stream pointer to next record
2399  $this->_pos += 4 + $length;
2400 
2401  // external sheet references provided for named cells
2402  if ($this->_version == self::XLS_BIFF8) {
2403  // offset: 0; size: 2; options
2404  $options = self::_GetInt2d($recordData, 0);
2405 
2406  // offset: 2; size: 2;
2407 
2408  // offset: 4; size: 2; not used
2409 
2410  // offset: 6; size: var
2411  $nameString = self::_readUnicodeStringShort(substr($recordData, 6));
2412 
2413  // offset: var; size: var; formula data
2414  $offset = 6 + $nameString['size'];
2415  $formula = $this->_getFormulaFromStructure(substr($recordData, $offset));
2416 
2417  $this->_externalNames[] = array(
2418  'name' => $nameString['value'],
2419  'formula' => $formula,
2420  );
2421  }
2422  }
2423 
2427  private function _readExternSheet()
2428  {
2429  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2430  $recordData = substr($this->_data, $this->_pos + 4, $length);
2431 
2432  // move stream pointer to next record
2433  $this->_pos += 4 + $length;
2434 
2435  // external sheet references provided for named cells
2436  if ($this->_version == self::XLS_BIFF8) {
2437  // offset: 0; size: 2; number of following ref structures
2438  $nm = self::_GetInt2d($recordData, 0);
2439  for ($i = 0; $i < $nm; ++$i) {
2440  $this->_ref[] = array(
2441  // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2442  'externalBookIndex' => self::_GetInt2d($recordData, 2 + 6 * $i),
2443  // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2444  'firstSheetIndex' => self::_GetInt2d($recordData, 4 + 6 * $i),
2445  // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2446  'lastSheetIndex' => self::_GetInt2d($recordData, 6 + 6 * $i),
2447  );
2448  }
2449  }
2450  }
2451 
2463  private function _readDefinedName()
2464  {
2465  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2466  $recordData = substr($this->_data, $this->_pos + 4, $length);
2467 
2468  // move stream pointer to next record
2469  $this->_pos += 4 + $length;
2470 
2471  if ($this->_version == self::XLS_BIFF8) {
2472  // retrieves named cells
2473 
2474  // offset: 0; size: 2; option flags
2475  $opts = self::_GetInt2d($recordData, 0);
2476 
2477  // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2478  $isBuiltInName = (0x0020 & $opts) >> 5;
2479 
2480  // offset: 2; size: 1; keyboard shortcut
2481 
2482  // offset: 3; size: 1; length of the name (character count)
2483  $nlen = ord($recordData{3});
2484 
2485  // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2486  // note: there can also be additional data, this is not included in $flen
2487  $flen = self::_GetInt2d($recordData, 4);
2488 
2489  // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2490  $scope = self::_GetInt2d($recordData, 8);
2491 
2492  // offset: 14; size: var; Name (Unicode string without length field)
2493  $string = self::_readUnicodeString(substr($recordData, 14), $nlen);
2494 
2495  // offset: var; size: $flen; formula data
2496  $offset = 14 + $string['size'];
2497  $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2498 
2499  try {
2500  $formula = $this->_getFormulaFromStructure($formulaStructure);
2501  } catch (Exception $e) {
2502  $formula = '';
2503  }
2504 
2505  $this->_definedname[] = array(
2506  'isBuiltInName' => $isBuiltInName,
2507  'name' => $string['value'],
2508  'formula' => $formula,
2509  'scope' => $scope,
2510  );
2511  }
2512  }
2513 
2517  private function _readMsoDrawingGroup()
2518  {
2519  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2520 
2521  // get spliced record data
2522  $splicedRecordData = $this->_getSplicedRecordData();
2523  $recordData = $splicedRecordData['recordData'];
2524 
2525  $this->_drawingGroupData .= $recordData;
2526  }
2527 
2539  private function _readSst()
2540  {
2541  // offset within (spliced) record data
2542  $pos = 0;
2543 
2544  // get spliced record data
2545  $splicedRecordData = $this->_getSplicedRecordData();
2546 
2547  $recordData = $splicedRecordData['recordData'];
2548  $spliceOffsets = $splicedRecordData['spliceOffsets'];
2549 
2550  // offset: 0; size: 4; total number of strings in the workbook
2551  $pos += 4;
2552 
2553  // offset: 4; size: 4; number of following strings ($nm)
2554  $nm = self::_GetInt4d($recordData, 4);
2555  $pos += 4;
2556 
2557  // loop through the Unicode strings (16-bit length)
2558  for ($i = 0; $i < $nm; ++$i) {
2559 
2560  // number of characters in the Unicode string
2561  $numChars = self::_GetInt2d($recordData, $pos);
2562  $pos += 2;
2563 
2564  // option flags
2565  $optionFlags = ord($recordData{$pos});
2566  ++$pos;
2567 
2568  // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2569  $isCompressed = (($optionFlags & 0x01) == 0) ;
2570 
2571  // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2572  $hasAsian = (($optionFlags & 0x04) != 0);
2573 
2574  // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
2575  $hasRichText = (($optionFlags & 0x08) != 0);
2576 
2577  if ($hasRichText) {
2578  // number of Rich-Text formatting runs
2579  $formattingRuns = self::_GetInt2d($recordData, $pos);
2580  $pos += 2;
2581  }
2582 
2583  if ($hasAsian) {
2584  // size of Asian phonetic setting
2585  $extendedRunLength = self::_GetInt4d($recordData, $pos);
2586  $pos += 4;
2587  }
2588 
2589  // expected byte length of character array if not split
2590  $len = ($isCompressed) ? $numChars : $numChars * 2;
2591 
2592  // look up limit position
2593  foreach ($spliceOffsets as $spliceOffset) {
2594  // it can happen that the string is empty, therefore we need
2595  // <= and not just <
2596  if ($pos <= $spliceOffset) {
2597  $limitpos = $spliceOffset;
2598  break;
2599  }
2600  }
2601 
2602  if ($pos + $len <= $limitpos) {
2603  // character array is not split between records
2604 
2605  $retstr = substr($recordData, $pos, $len);
2606  $pos += $len;
2607 
2608  } else {
2609  // character array is split between records
2610 
2611  // first part of character array
2612  $retstr = substr($recordData, $pos, $limitpos - $pos);
2613 
2614  $bytesRead = $limitpos - $pos;
2615 
2616  // remaining characters in Unicode string
2617  $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
2618 
2619  $pos = $limitpos;
2620 
2621  // keep reading the characters
2622  while ($charsLeft > 0) {
2623 
2624  // look up next limit position, in case the string span more than one continue record
2625  foreach ($spliceOffsets as $spliceOffset) {
2626  if ($pos < $spliceOffset) {
2627  $limitpos = $spliceOffset;
2628  break;
2629  }
2630  }
2631 
2632  // repeated option flags
2633  // OpenOffice.org documentation 5.21
2634  $option = ord($recordData{$pos});
2635  ++$pos;
2636 
2637  if ($isCompressed && ($option == 0)) {
2638  // 1st fragment compressed
2639  // this fragment compressed
2640  $len = min($charsLeft, $limitpos - $pos);
2641  $retstr .= substr($recordData, $pos, $len);
2642  $charsLeft -= $len;
2643  $isCompressed = true;
2644 
2645  } elseif (!$isCompressed && ($option != 0)) {
2646  // 1st fragment uncompressed
2647  // this fragment uncompressed
2648  $len = min($charsLeft * 2, $limitpos - $pos);
2649  $retstr .= substr($recordData, $pos, $len);
2650  $charsLeft -= $len / 2;
2651  $isCompressed = false;
2652 
2653  } elseif (!$isCompressed && ($option == 0)) {
2654  // 1st fragment uncompressed
2655  // this fragment compressed
2656  $len = min($charsLeft, $limitpos - $pos);
2657  for ($j = 0; $j < $len; ++$j) {
2658  $retstr .= $recordData{$pos + $j} . chr(0);
2659  }
2660  $charsLeft -= $len;
2661  $isCompressed = false;
2662 
2663  } else {
2664  // 1st fragment compressed
2665  // this fragment uncompressed
2666  $newstr = '';
2667  for ($j = 0; $j < strlen($retstr); ++$j) {
2668  $newstr .= $retstr[$j] . chr(0);
2669  }
2670  $retstr = $newstr;
2671  $len = min($charsLeft * 2, $limitpos - $pos);
2672  $retstr .= substr($recordData, $pos, $len);
2673  $charsLeft -= $len / 2;
2674  $isCompressed = false;
2675  }
2676 
2677  $pos += $len;
2678  }
2679  }
2680 
2681  // convert to UTF-8
2682  $retstr = self::_encodeUTF16($retstr, $isCompressed);
2683 
2684  // read additional Rich-Text information, if any
2685  $fmtRuns = array();
2686  if ($hasRichText) {
2687  // list of formatting runs
2688  for ($j = 0; $j < $formattingRuns; ++$j) {
2689  // first formatted character; zero-based
2690  $charPos = self::_GetInt2d($recordData, $pos + $j * 4);
2691 
2692  // index to font record
2693  $fontIndex = self::_GetInt2d($recordData, $pos + 2 + $j * 4);
2694 
2695  $fmtRuns[] = array(
2696  'charPos' => $charPos,
2697  'fontIndex' => $fontIndex,
2698  );
2699  }
2700  $pos += 4 * $formattingRuns;
2701  }
2702 
2703  // read additional Asian phonetics information, if any
2704  if ($hasAsian) {
2705  // For Asian phonetic settings, we skip the extended string data
2706  $pos += $extendedRunLength;
2707  }
2708 
2709  // store the shared sting
2710  $this->_sst[] = array(
2711  'value' => $retstr,
2712  'fmtRuns' => $fmtRuns,
2713  );
2714  }
2715 
2716  // _getSplicedRecordData() takes care of moving current position in data stream
2717  }
2718 
2722  private function _readPrintGridlines()
2723  {
2724  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2725  $recordData = substr($this->_data, $this->_pos + 4, $length);
2726 
2727  // move stream pointer to next record
2728  $this->_pos += 4 + $length;
2729 
2730  if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
2731  // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
2732  $printGridlines = (bool) self::_GetInt2d($recordData, 0);
2733  $this->_phpSheet->setPrintGridlines($printGridlines);
2734  }
2735  }
2736 
2740  private function _readDefaultRowHeight()
2741  {
2742  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2743  $recordData = substr($this->_data, $this->_pos + 4, $length);
2744 
2745  // move stream pointer to next record
2746  $this->_pos += 4 + $length;
2747 
2748  // offset: 0; size: 2; option flags
2749  // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
2750  $height = self::_GetInt2d($recordData, 2);
2751  $this->_phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
2752  }
2753 
2757  private function _readSheetPr()
2758  {
2759  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2760  $recordData = substr($this->_data, $this->_pos + 4, $length);
2761 
2762  // move stream pointer to next record
2763  $this->_pos += 4 + $length;
2764 
2765  // offset: 0; size: 2
2766 
2767  // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
2768  $isSummaryBelow = (0x0040 & self::_GetInt2d($recordData, 0)) >> 6;
2769  $this->_phpSheet->setShowSummaryBelow($isSummaryBelow);
2770 
2771  // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
2772  $isSummaryRight = (0x0080 & self::_GetInt2d($recordData, 0)) >> 7;
2773  $this->_phpSheet->setShowSummaryRight($isSummaryRight);
2774 
2775  // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
2776  // this corresponds to radio button setting in page setup dialog in Excel
2777  $this->_isFitToPages = (bool) ((0x0100 & self::_GetInt2d($recordData, 0)) >> 8);
2778  }
2779 
2783  private function _readHorizontalPageBreaks()
2784  {
2785  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2786  $recordData = substr($this->_data, $this->_pos + 4, $length);
2787 
2788  // move stream pointer to next record
2789  $this->_pos += 4 + $length;
2790 
2791  if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
2792 
2793  // offset: 0; size: 2; number of the following row index structures
2794  $nm = self::_GetInt2d($recordData, 0);
2795 
2796  // offset: 2; size: 6 * $nm; list of $nm row index structures
2797  for ($i = 0; $i < $nm; ++$i) {
2798  $r = self::_GetInt2d($recordData, 2 + 6 * $i);
2799  $cf = self::_GetInt2d($recordData, 2 + 6 * $i + 2);
2800  $cl = self::_GetInt2d($recordData, 2 + 6 * $i + 4);
2801 
2802  // not sure why two column indexes are necessary?
2803  $this->_phpSheet->setBreakByColumnAndRow($cf, $r, PHPExcel_Worksheet::BREAK_ROW);
2804  }
2805  }
2806  }
2807 
2811  private function _readVerticalPageBreaks()
2812  {
2813  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2814  $recordData = substr($this->_data, $this->_pos + 4, $length);
2815 
2816  // move stream pointer to next record
2817  $this->_pos += 4 + $length;
2818 
2819  if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
2820  // offset: 0; size: 2; number of the following column index structures
2821  $nm = self::_GetInt2d($recordData, 0);
2822 
2823  // offset: 2; size: 6 * $nm; list of $nm row index structures
2824  for ($i = 0; $i < $nm; ++$i) {
2825  $c = self::_GetInt2d($recordData, 2 + 6 * $i);
2826  $rf = self::_GetInt2d($recordData, 2 + 6 * $i + 2);
2827  $rl = self::_GetInt2d($recordData, 2 + 6 * $i + 4);
2828 
2829  // not sure why two row indexes are necessary?
2830  $this->_phpSheet->setBreakByColumnAndRow($c, $rf, PHPExcel_Worksheet::BREAK_COLUMN);
2831  }
2832  }
2833  }
2834 
2838  private function _readHeader()
2839  {
2840  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2841  $recordData = substr($this->_data, $this->_pos + 4, $length);
2842 
2843  // move stream pointer to next record
2844  $this->_pos += 4 + $length;
2845 
2846  if (!$this->_readDataOnly) {
2847  // offset: 0; size: var
2848  // realized that $recordData can be empty even when record exists
2849  if ($recordData) {
2850  if ($this->_version == self::XLS_BIFF8) {
2851  $string = self::_readUnicodeStringLong($recordData);
2852  } else {
2853  $string = $this->_readByteStringShort($recordData);
2854  }
2855 
2856  $this->_phpSheet->getHeaderFooter()->setOddHeader($string['value']);
2857  $this->_phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
2858  }
2859  }
2860  }
2861 
2865  private function _readFooter()
2866  {
2867  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2868  $recordData = substr($this->_data, $this->_pos + 4, $length);
2869 
2870  // move stream pointer to next record
2871  $this->_pos += 4 + $length;
2872 
2873  if (!$this->_readDataOnly) {
2874  // offset: 0; size: var
2875  // realized that $recordData can be empty even when record exists
2876  if ($recordData) {
2877  if ($this->_version == self::XLS_BIFF8) {
2878  $string = self::_readUnicodeStringLong($recordData);
2879  } else {
2880  $string = $this->_readByteStringShort($recordData);
2881  }
2882  $this->_phpSheet->getHeaderFooter()->setOddFooter($string['value']);
2883  $this->_phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
2884  }
2885  }
2886  }
2887 
2891  private function _readHcenter()
2892  {
2893  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2894  $recordData = substr($this->_data, $this->_pos + 4, $length);
2895 
2896  // move stream pointer to next record
2897  $this->_pos += 4 + $length;
2898 
2899  if (!$this->_readDataOnly) {
2900  // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
2901  $isHorizontalCentered = (bool) self::_GetInt2d($recordData, 0);
2902 
2903  $this->_phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
2904  }
2905  }
2906 
2910  private function _readVcenter()
2911  {
2912  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2913  $recordData = substr($this->_data, $this->_pos + 4, $length);
2914 
2915  // move stream pointer to next record
2916  $this->_pos += 4 + $length;
2917 
2918  if (!$this->_readDataOnly) {
2919  // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
2920  $isVerticalCentered = (bool) self::_GetInt2d($recordData, 0);
2921 
2922  $this->_phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
2923  }
2924  }
2925 
2929  private function _readLeftMargin()
2930  {
2931  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2932  $recordData = substr($this->_data, $this->_pos + 4, $length);
2933 
2934  // move stream pointer to next record
2935  $this->_pos += 4 + $length;
2936 
2937  if (!$this->_readDataOnly) {
2938  // offset: 0; size: 8
2939  $this->_phpSheet->getPageMargins()->setLeft(self::_extractNumber($recordData));
2940  }
2941  }
2942 
2946  private function _readRightMargin()
2947  {
2948  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2949  $recordData = substr($this->_data, $this->_pos + 4, $length);
2950 
2951  // move stream pointer to next record
2952  $this->_pos += 4 + $length;
2953 
2954  if (!$this->_readDataOnly) {
2955  // offset: 0; size: 8
2956  $this->_phpSheet->getPageMargins()->setRight(self::_extractNumber($recordData));
2957  }
2958  }
2959 
2963  private function _readTopMargin()
2964  {
2965  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2966  $recordData = substr($this->_data, $this->_pos + 4, $length);
2967 
2968  // move stream pointer to next record
2969  $this->_pos += 4 + $length;
2970 
2971  if (!$this->_readDataOnly) {
2972  // offset: 0; size: 8
2973  $this->_phpSheet->getPageMargins()->setTop(self::_extractNumber($recordData));
2974  }
2975  }
2976 
2980  private function _readBottomMargin()
2981  {
2982  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2983  $recordData = substr($this->_data, $this->_pos + 4, $length);
2984 
2985  // move stream pointer to next record
2986  $this->_pos += 4 + $length;
2987 
2988  if (!$this->_readDataOnly) {
2989  // offset: 0; size: 8
2990  $this->_phpSheet->getPageMargins()->setBottom(self::_extractNumber($recordData));
2991  }
2992  }
2993 
2997  private function _readPageSetup()
2998  {
2999  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3000  $recordData = substr($this->_data, $this->_pos + 4, $length);
3001 
3002  // move stream pointer to next record
3003  $this->_pos += 4 + $length;
3004 
3005  if (!$this->_readDataOnly) {
3006  // offset: 0; size: 2; paper size
3007  $paperSize = self::_GetInt2d($recordData, 0);
3008 
3009  // offset: 2; size: 2; scaling factor
3010  $scale = self::_GetInt2d($recordData, 2);
3011 
3012  // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3013  $fitToWidth = self::_GetInt2d($recordData, 6);
3014 
3015  // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3016  $fitToHeight = self::_GetInt2d($recordData, 8);
3017 
3018  // offset: 10; size: 2; option flags
3019 
3020  // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
3021  $isPortrait = (0x0002 & self::_GetInt2d($recordData, 10)) >> 1;
3022 
3023  // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3024  // when this bit is set, do not use flags for those properties
3025  $isNotInit = (0x0004 & self::_GetInt2d($recordData, 10)) >> 2;
3026 
3027  if (!$isNotInit) {
3028  $this->_phpSheet->getPageSetup()->setPaperSize($paperSize);
3029  switch ($isPortrait) {
3030  case 0: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE); break;
3031  case 1: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT); break;
3032  }
3033 
3034  $this->_phpSheet->getPageSetup()->setScale($scale, false);
3035  $this->_phpSheet->getPageSetup()->setFitToPage((bool) $this->_isFitToPages);
3036  $this->_phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3037  $this->_phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3038  }
3039 
3040  // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3041  $marginHeader = self::_extractNumber(substr($recordData, 16, 8));
3042  $this->_phpSheet->getPageMargins()->setHeader($marginHeader);
3043 
3044  // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3045  $marginFooter = self::_extractNumber(substr($recordData, 24, 8));
3046  $this->_phpSheet->getPageMargins()->setFooter($marginFooter);
3047  }
3048  }
3049 
3054  private function _readProtect()
3055  {
3056  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3057  $recordData = substr($this->_data, $this->_pos + 4, $length);
3058 
3059  // move stream pointer to next record
3060  $this->_pos += 4 + $length;
3061 
3062  if ($this->_readDataOnly) {
3063  return;
3064  }
3065 
3066  // offset: 0; size: 2;
3067 
3068  // bit 0, mask 0x01; 1 = sheet is protected
3069  $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3070  $this->_phpSheet->getProtection()->setSheet((bool)$bool);
3071  }
3072 
3076  private function _readScenProtect()
3077  {
3078  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3079  $recordData = substr($this->_data, $this->_pos + 4, $length);
3080 
3081  // move stream pointer to next record
3082  $this->_pos += 4 + $length;
3083 
3084  if ($this->_readDataOnly) {
3085  return;
3086  }
3087 
3088  // offset: 0; size: 2;
3089 
3090  // bit: 0, mask 0x01; 1 = scenarios are protected
3091  $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3092 
3093  $this->_phpSheet->getProtection()->setScenarios((bool)$bool);
3094  }
3095 
3099  private function _readObjectProtect()
3100  {
3101  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3102  $recordData = substr($this->_data, $this->_pos + 4, $length);
3103 
3104  // move stream pointer to next record
3105  $this->_pos += 4 + $length;
3106 
3107  if ($this->_readDataOnly) {
3108  return;
3109  }
3110 
3111  // offset: 0; size: 2;
3112 
3113  // bit: 0, mask 0x01; 1 = objects are protected
3114  $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3115 
3116  $this->_phpSheet->getProtection()->setObjects((bool)$bool);
3117  }
3118 
3122  private function _readPassword()
3123  {
3124  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3125  $recordData = substr($this->_data, $this->_pos + 4, $length);
3126 
3127  // move stream pointer to next record
3128  $this->_pos += 4 + $length;
3129 
3130  if (!$this->_readDataOnly) {
3131  // offset: 0; size: 2; 16-bit hash value of password
3132  $password = strtoupper(dechex(self::_GetInt2d($recordData, 0))); // the hashed password
3133  $this->_phpSheet->getProtection()->setPassword($password, true);
3134  }
3135  }
3136 
3140  private function _readDefColWidth()
3141  {
3142  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3143  $recordData = substr($this->_data, $this->_pos + 4, $length);
3144 
3145  // move stream pointer to next record
3146  $this->_pos += 4 + $length;
3147 
3148  // offset: 0; size: 2; default column width
3149  $width = self::_GetInt2d($recordData, 0);
3150  if ($width != 8) {
3151  $this->_phpSheet->getDefaultColumnDimension()->setWidth($width);
3152  }
3153  }
3154 
3158  private function _readColInfo()
3159  {
3160  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3161  $recordData = substr($this->_data, $this->_pos + 4, $length);
3162 
3163  // move stream pointer to next record
3164  $this->_pos += 4 + $length;
3165 
3166  if (!$this->_readDataOnly) {
3167  // offset: 0; size: 2; index to first column in range
3168  $fc = self::_GetInt2d($recordData, 0); // first column index
3169 
3170  // offset: 2; size: 2; index to last column in range
3171  $lc = self::_GetInt2d($recordData, 2); // first column index
3172 
3173  // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3174  $width = self::_GetInt2d($recordData, 4);
3175 
3176  // offset: 6; size: 2; index to XF record for default column formatting
3177  $xfIndex = self::_GetInt2d($recordData, 6);
3178 
3179  // offset: 8; size: 2; option flags
3180 
3181  // bit: 0; mask: 0x0001; 1= columns are hidden
3182  $isHidden = (0x0001 & self::_GetInt2d($recordData, 8)) >> 0;
3183 
3184  // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3185  $level = (0x0700 & self::_GetInt2d($recordData, 8)) >> 8;
3186 
3187  // bit: 12; mask: 0x1000; 1 = collapsed
3188  $isCollapsed = (0x1000 & self::_GetInt2d($recordData, 8)) >> 12;
3189 
3190  // offset: 10; size: 2; not used
3191 
3192  for ($i = $fc; $i <= $lc; ++$i) {
3193  if ($lc == 255 || $lc == 256) {
3194  $this->_phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3195  break;
3196  }
3197  $this->_phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3198  $this->_phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3199  $this->_phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3200  $this->_phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3201  $this->_phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3202  }
3203  }
3204  }
3205 
3216  private function _readRow()
3217  {
3218  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3219  $recordData = substr($this->_data, $this->_pos + 4, $length);
3220 
3221  // move stream pointer to next record
3222  $this->_pos += 4 + $length;
3223 
3224  if (!$this->_readDataOnly) {
3225  // offset: 0; size: 2; index of this row
3226  $r = self::_GetInt2d($recordData, 0);
3227 
3228  // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3229 
3230  // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3231 
3232  // offset: 6; size: 2;
3233 
3234  // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3235  $height = (0x7FFF & self::_GetInt2d($recordData, 6)) >> 0;
3236 
3237  // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3238  $useDefaultHeight = (0x8000 & self::_GetInt2d($recordData, 6)) >> 15;
3239 
3240  if (!$useDefaultHeight) {
3241  $this->_phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3242  }
3243 
3244  // offset: 8; size: 2; not used
3245 
3246  // offset: 10; size: 2; not used in BIFF5-BIFF8
3247 
3248  // offset: 12; size: 4; option flags and default row formatting
3249 
3250  // bit: 2-0: mask: 0x00000007; outline level of the row
3251  $level = (0x00000007 & self::_GetInt4d($recordData, 12)) >> 0;
3252  $this->_phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3253 
3254  // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3255  $isCollapsed = (0x00000010 & self::_GetInt4d($recordData, 12)) >> 4;
3256  $this->_phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3257 
3258  // bit: 5; mask: 0x00000020; 1 = row is hidden
3259  $isHidden = (0x00000020 & self::_GetInt4d($recordData, 12)) >> 5;
3260  $this->_phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3261 
3262  // bit: 7; mask: 0x00000080; 1 = row has explicit format
3263  $hasExplicitFormat = (0x00000080 & self::_GetInt4d($recordData, 12)) >> 7;
3264 
3265  // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3266  $xfIndex = (0x0FFF0000 & self::_GetInt4d($recordData, 12)) >> 16;
3267 
3268  if ($hasExplicitFormat) {
3269  $this->_phpSheet->getRowDimension($r + 1)->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3270  }
3271  }
3272  }
3273 
3285  private function _readRk()
3286  {
3287  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3288  $recordData = substr($this->_data, $this->_pos + 4, $length);
3289 
3290  // move stream pointer to next record
3291  $this->_pos += 4 + $length;
3292 
3293  // offset: 0; size: 2; index to row
3294  $row = self::_GetInt2d($recordData, 0);
3295 
3296  // offset: 2; size: 2; index to column
3297  $column = self::_GetInt2d($recordData, 2);
3298  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3299 
3300  // Read cell?
3301  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3302  // offset: 4; size: 2; index to XF record
3303  $xfIndex = self::_GetInt2d($recordData, 4);
3304 
3305  // offset: 6; size: 4; RK value
3306  $rknum = self::_GetInt4d($recordData, 6);
3307  $numValue = self::_GetIEEE754($rknum);
3308 
3309  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3310  if (!$this->_readDataOnly) {
3311  // add style information
3312  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3313  }
3314 
3315  // add cell
3316  $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3317  }
3318  }
3319 
3329  private function _readLabelSst()
3330  {
3331  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3332  $recordData = substr($this->_data, $this->_pos + 4, $length);
3333 
3334  // move stream pointer to next record
3335  $this->_pos += 4 + $length;
3336 
3337  // offset: 0; size: 2; index to row
3338  $row = self::_GetInt2d($recordData, 0);
3339 
3340  // offset: 2; size: 2; index to column
3341  $column = self::_GetInt2d($recordData, 2);
3342  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3343 
3344  // Read cell?
3345  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3346  // offset: 4; size: 2; index to XF record
3347  $xfIndex = self::_GetInt2d($recordData, 4);
3348 
3349  // offset: 6; size: 4; index to SST record
3350  $index = self::_GetInt4d($recordData, 6);
3351 
3352  // add cell
3353  if (($fmtRuns = $this->_sst[$index]['fmtRuns']) && !$this->_readDataOnly) {
3354  // then we should treat as rich text
3355  $richText = new PHPExcel_RichText();
3356  $charPos = 0;
3357  $sstCount = count($this->_sst[$index]['fmtRuns']);
3358  for ($i = 0; $i <= $sstCount; ++$i) {
3359  if (isset($fmtRuns[$i])) {
3360  $text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3361  $charPos = $fmtRuns[$i]['charPos'];
3362  } else {
3363  $text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, PHPExcel_Shared_String::CountCharacters($this->_sst[$index]['value']));
3364  }
3365 
3366  if (PHPExcel_Shared_String::CountCharacters($text) > 0) {
3367  if ($i == 0) { // first text run, no style
3368  $richText->createText($text);
3369  } else {
3370  $textRun = $richText->createTextRun($text);
3371  if (isset($fmtRuns[$i - 1])) {
3372  if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3373  $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3374  } else {
3375  // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3376  // check the OpenOffice documentation of the FONT record
3377  $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3378  }
3379  $textRun->setFont(clone $this->_objFonts[$fontIndex]);
3380  }
3381  }
3382  }
3383  }
3384  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3385  $cell->setValueExplicit($richText, PHPExcel_Cell_DataType::TYPE_STRING);
3386  } else {
3387  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3388  $cell->setValueExplicit($this->_sst[$index]['value'], PHPExcel_Cell_DataType::TYPE_STRING);
3389  }
3390 
3391  if (!$this->_readDataOnly) {
3392  // add style information
3393  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3394  }
3395  }
3396  }
3397 
3406  private function _readMulRk()
3407  {
3408  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3409  $recordData = substr($this->_data, $this->_pos + 4, $length);
3410 
3411  // move stream pointer to next record
3412  $this->_pos += 4 + $length;
3413 
3414  // offset: 0; size: 2; index to row
3415  $row = self::_GetInt2d($recordData, 0);
3416 
3417  // offset: 2; size: 2; index to first column
3418  $colFirst = self::_GetInt2d($recordData, 2);
3419 
3420  // offset: var; size: 2; index to last column
3421  $colLast = self::_GetInt2d($recordData, $length - 2);
3422  $columns = $colLast - $colFirst + 1;
3423 
3424  // offset within record data
3425  $offset = 4;
3426 
3427  for ($i = 0; $i < $columns; ++$i) {
3428  $columnString = PHPExcel_Cell::stringFromColumnIndex($colFirst + $i);
3429 
3430  // Read cell?
3431  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3432 
3433  // offset: var; size: 2; index to XF record
3434  $xfIndex = self::_GetInt2d($recordData, $offset);
3435 
3436  // offset: var; size: 4; RK value
3437  $numValue = self::_GetIEEE754(self::_GetInt4d($recordData, $offset + 2));
3438  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3439  if (!$this->_readDataOnly) {
3440  // add style
3441  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3442  }
3443 
3444  // add cell value
3445  $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3446  }
3447 
3448  $offset += 6;
3449  }
3450  }
3451 
3460  private function _readNumber()
3461  {
3462  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3463  $recordData = substr($this->_data, $this->_pos + 4, $length);
3464 
3465  // move stream pointer to next record
3466  $this->_pos += 4 + $length;
3467 
3468  // offset: 0; size: 2; index to row
3469  $row = self::_GetInt2d($recordData, 0);
3470 
3471  // offset: 2; size 2; index to column
3472  $column = self::_GetInt2d($recordData, 2);
3473  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3474 
3475  // Read cell?
3476  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3477  // offset 4; size: 2; index to XF record
3478  $xfIndex = self::_GetInt2d($recordData, 4);
3479 
3480  $numValue = self::_extractNumber(substr($recordData, 6, 8));
3481 
3482  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3483  if (!$this->_readDataOnly) {
3484  // add cell style
3485  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3486  }
3487 
3488  // add cell value
3489  $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3490  }
3491  }
3492 
3501  private function _readFormula()
3502  {
3503  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3504  $recordData = substr($this->_data, $this->_pos + 4, $length);
3505 
3506  // move stream pointer to next record
3507  $this->_pos += 4 + $length;
3508 
3509  // offset: 0; size: 2; row index
3510  $row = self::_GetInt2d($recordData, 0);
3511 
3512  // offset: 2; size: 2; col index
3513  $column = self::_GetInt2d($recordData, 2);
3514  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3515 
3516  // offset: 20: size: variable; formula structure
3517  $formulaStructure = substr($recordData, 20);
3518 
3519  // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3520  $options = self::_GetInt2d($recordData, 14);
3521 
3522  // bit: 0; mask: 0x0001; 1 = recalculate always
3523  // bit: 1; mask: 0x0002; 1 = calculate on open
3524  // bit: 2; mask: 0x0008; 1 = part of a shared formula
3525  $isPartOfSharedFormula = (bool) (0x0008 & $options);
3526 
3527  // WARNING:
3528  // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3529  // the formula data may be ordinary formula data, therefore we need to check
3530  // explicitly for the tExp token (0x01)
3531  $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01;
3532 
3533  if ($isPartOfSharedFormula) {
3534  // part of shared formula which means there will be a formula with a tExp token and nothing else
3535  // get the base cell, grab tExp token
3536  $baseRow = self::_GetInt2d($formulaStructure, 3);
3537  $baseCol = self::_GetInt2d($formulaStructure, 5);
3538  $this->_baseCell = PHPExcel_Cell::stringFromColumnIndex($baseCol). ($baseRow + 1);
3539  }
3540 
3541  // Read cell?
3542  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3543 
3544  if ($isPartOfSharedFormula) {
3545  // formula is added to this cell after the sheet has been read
3546  $this->_sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell;
3547  }
3548 
3549  // offset: 16: size: 4; not used
3550 
3551  // offset: 4; size: 2; XF index
3552  $xfIndex = self::_GetInt2d($recordData, 4);
3553 
3554  // offset: 6; size: 8; result of the formula
3555  if ( (ord($recordData{6}) == 0)
3556  && (ord($recordData{12}) == 255)
3557  && (ord($recordData{13}) == 255) ) {
3558 
3559  // String formula. Result follows in appended STRING record
3561 
3562  // read possible SHAREDFMLA record
3563  $code = self::_GetInt2d($this->_data, $this->_pos);
3564  if ($code == self::XLS_Type_SHAREDFMLA) {
3565  $this->_readSharedFmla();
3566  }
3567 
3568  // read STRING record
3569  $value = $this->_readString();
3570 
3571  } elseif ((ord($recordData{6}) == 1)
3572  && (ord($recordData{12}) == 255)
3573  && (ord($recordData{13}) == 255)) {
3574 
3575  // Boolean formula. Result is in +2; 0=false, 1=true
3577  $value = (bool) ord($recordData{8});
3578 
3579  } elseif ((ord($recordData{6}) == 2)
3580  && (ord($recordData{12}) == 255)
3581  && (ord($recordData{13}) == 255)) {
3582 
3583  // Error formula. Error code is in +2
3585  $value = self::_mapErrorCode(ord($recordData{8}));
3586 
3587  } elseif ((ord($recordData{6}) == 3)
3588  && (ord($recordData{12}) == 255)
3589  && (ord($recordData{13}) == 255)) {
3590 
3591  // Formula result is a null string
3593  $value = '';
3594 
3595  } else {
3596 
3597  // forumla result is a number, first 14 bytes like _NUMBER record
3599  $value = self::_extractNumber(substr($recordData, 6, 8));
3600 
3601  }
3602 
3603  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3604  if (!$this->_readDataOnly) {
3605  // add cell style
3606  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3607  }
3608 
3609  // store the formula
3610  if (!$isPartOfSharedFormula) {
3611  // not part of shared formula
3612  // add cell value. If we can read formula, populate with formula, otherwise just used cached value
3613  try {
3614  if ($this->_version != self::XLS_BIFF8) {
3615  throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
3616  }
3617  $formula = $this->_getFormulaFromStructure($formulaStructure); // get formula in human language
3618  $cell->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
3619 
3620  } catch (Exception $e) {
3621  $cell->setValueExplicit($value, $dataType);
3622  }
3623  } else {
3624  if ($this->_version == self::XLS_BIFF8) {
3625  // do nothing at this point, formula id added later in the code
3626  } else {
3627  $cell->setValueExplicit($value, $dataType);
3628  }
3629  }
3630 
3631  // store the cached calculated value
3632  $cell->setCalculatedValue($value);
3633  }
3634  }
3635 
3641  private function _readSharedFmla()
3642  {
3643  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3644  $recordData = substr($this->_data, $this->_pos + 4, $length);
3645 
3646  // move stream pointer to next record
3647  $this->_pos += 4 + $length;
3648 
3649  // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
3650  $cellRange = substr($recordData, 0, 6);
3651  $cellRange = $this->_readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
3652 
3653  // offset: 6, size: 1; not used
3654 
3655  // offset: 7, size: 1; number of existing FORMULA records for this shared formula
3656  $no = ord($recordData{7});
3657 
3658  // offset: 8, size: var; Binary token array of the shared formula
3659  $formula = substr($recordData, 8);
3660 
3661  // at this point we only store the shared formula for later use
3662  $this->_sharedFormulas[$this->_baseCell] = $formula;
3663 
3664  }
3665 
3673  private function _readString()
3674  {
3675  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3676  $recordData = substr($this->_data, $this->_pos + 4, $length);
3677 
3678  // move stream pointer to next record
3679  $this->_pos += 4 + $length;
3680 
3681  if ($this->_version == self::XLS_BIFF8) {
3682  $string = self::_readUnicodeStringLong($recordData);
3683  $value = $string['value'];
3684  } else {
3685  $string = $this->_readByteStringLong($recordData);
3686  $value = $string['value'];
3687  }
3688 
3689  return $value;
3690  }
3691 
3700  private function _readBoolErr()
3701  {
3702  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3703  $recordData = substr($this->_data, $this->_pos + 4, $length);
3704 
3705  // move stream pointer to next record
3706  $this->_pos += 4 + $length;
3707 
3708  // offset: 0; size: 2; row index
3709  $row = self::_GetInt2d($recordData, 0);
3710 
3711  // offset: 2; size: 2; column index
3712  $column = self::_GetInt2d($recordData, 2);
3713  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3714 
3715  // Read cell?
3716  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3717  // offset: 4; size: 2; index to XF record
3718  $xfIndex = self::_GetInt2d($recordData, 4);
3719 
3720  // offset: 6; size: 1; the boolean value or error value
3721  $boolErr = ord($recordData{6});
3722 
3723  // offset: 7; size: 1; 0=boolean; 1=error
3724  $isError = ord($recordData{7});
3725 
3726  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3727  switch ($isError) {
3728  case 0: // boolean
3729  $value = (bool) $boolErr;
3730 
3731  // add cell value
3732  $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_BOOL);
3733  break;
3734 
3735  case 1: // error type
3736  $value = self::_mapErrorCode($boolErr);
3737 
3738  // add cell value
3739  $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_ERROR);
3740  break;
3741  }
3742 
3743  if (!$this->_readDataOnly) {
3744  // add cell style
3745  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3746  }
3747  }
3748  }
3749 
3758  private function _readMulBlank()
3759  {
3760  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3761  $recordData = substr($this->_data, $this->_pos + 4, $length);
3762 
3763  // move stream pointer to next record
3764  $this->_pos += 4 + $length;
3765 
3766  // offset: 0; size: 2; index to row
3767  $row = self::_GetInt2d($recordData, 0);
3768 
3769  // offset: 2; size: 2; index to first column
3770  $fc = self::_GetInt2d($recordData, 2);
3771 
3772  // offset: 4; size: 2 x nc; list of indexes to XF records
3773  // add style information
3774  if (!$this->_readDataOnly) {
3775  for ($i = 0; $i < $length / 2 - 3; ++$i) {
3776  $columnString = PHPExcel_Cell::stringFromColumnIndex($fc + $i);
3777 
3778  // Read cell?
3779  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3780  $xfIndex = self::_GetInt2d($recordData, 4 + 2 * $i);
3781  $this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3782  }
3783  }
3784  }
3785 
3786  // offset: 6; size 2; index to last column (not needed)
3787  }
3788 
3799  private function _readLabel()
3800  {
3801  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3802  $recordData = substr($this->_data, $this->_pos + 4, $length);
3803 
3804  // move stream pointer to next record
3805  $this->_pos += 4 + $length;
3806 
3807  // offset: 0; size: 2; index to row
3808  $row = self::_GetInt2d($recordData, 0);
3809 
3810  // offset: 2; size: 2; index to column
3811  $column = self::_GetInt2d($recordData, 2);
3812  $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3813 
3814  // Read cell?
3815  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3816  // offset: 4; size: 2; XF index
3817  $xfIndex = self::_GetInt2d($recordData, 4);
3818 
3819  // add cell value
3820  // todo: what if string is very long? continue record
3821  if ($this->_version == self::XLS_BIFF8) {
3822  $string = self::_readUnicodeStringLong(substr($recordData, 6));
3823  $value = $string['value'];
3824  } else {
3825  $string = $this->_readByteStringLong(substr($recordData, 6));
3826  $value = $string['value'];
3827  }
3828  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3829  $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
3830 
3831  if (!$this->_readDataOnly) {
3832  // add cell style
3833  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3834  }
3835  }
3836  }
3837 
3841  private function _readBlank()
3842  {
3843  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3844  $recordData = substr($this->_data, $this->_pos + 4, $length);
3845 
3846  // move stream pointer to next record
3847  $this->_pos += 4 + $length;
3848 
3849  // offset: 0; size: 2; row index
3850  $row = self::_GetInt2d($recordData, 0);
3851 
3852  // offset: 2; size: 2; col index
3853  $col = self::_GetInt2d($recordData, 2);
3854  $columnString = PHPExcel_Cell::stringFromColumnIndex($col);
3855 
3856  // Read cell?
3857  if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3858  // offset: 4; size: 2; XF index
3859  $xfIndex = self::_GetInt2d($recordData, 4);
3860 
3861  // add style information
3862  if (!$this->_readDataOnly) {
3863  $this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3864  }
3865  }
3866 
3867  }
3868 
3872  private function _readMsoDrawing()
3873  {
3874  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3875 
3876  // get spliced record data
3877  $splicedRecordData = $this->_getSplicedRecordData();
3878  $recordData = $splicedRecordData['recordData'];
3879 
3880  $this->_drawingData .= $recordData;
3881  }
3882 
3886  private function _readObj()
3887  {
3888  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3889  $recordData = substr($this->_data, $this->_pos + 4, $length);
3890 
3891  // move stream pointer to next record
3892  $this->_pos += 4 + $length;
3893 
3894  if ($this->_readDataOnly || $this->_version != self::XLS_BIFF8) {
3895  return;
3896  }
3897 
3898  // recordData consists of an array of subrecords looking like this:
3899  // ft: 2 bytes; ftCmo type (0x15)
3900  // cb: 2 bytes; size in bytes of ftCmo data
3901  // ot: 2 bytes; Object Type
3902  // id: 2 bytes; Object id number
3903  // grbit: 2 bytes; Option Flags
3904  // data: var; subrecord data
3905 
3906  // for now, we are just interested in the second subrecord containing the object type
3907  $ftCmoType = self::_GetInt2d($recordData, 0);
3908  $cbCmoSize = self::_GetInt2d($recordData, 2);
3909  $otObjType = self::_GetInt2d($recordData, 4);
3910  $idObjID = self::_GetInt2d($recordData, 6);
3911  $grbitOpts = self::_GetInt2d($recordData, 6);
3912 
3913  $this->_objs[] = array(
3914  'ftCmoType' => $ftCmoType,
3915  'cbCmoSize' => $cbCmoSize,
3916  'otObjType' => $otObjType,
3917  'idObjID' => $idObjID,
3918  'grbitOpts' => $grbitOpts
3919  );
3920  $this->textObjRef = $idObjID;
3921 
3922 // echo '<b>_readObj()</b><br />';
3923 // var_dump(end($this->_objs));
3924 // echo '<br />';
3925  }
3926 
3930  private function _readWindow2()
3931  {
3932  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3933  $recordData = substr($this->_data, $this->_pos + 4, $length);
3934 
3935  // move stream pointer to next record
3936  $this->_pos += 4 + $length;
3937 
3938  // offset: 0; size: 2; option flags
3939  $options = self::_GetInt2d($recordData, 0);
3940 
3941  // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
3942  $showGridlines = (bool) ((0x0002 & $options) >> 1);
3943  $this->_phpSheet->setShowGridlines($showGridlines);
3944 
3945  // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
3946  $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
3947  $this->_phpSheet->setShowRowColHeaders($showRowColHeaders);
3948 
3949  // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
3950  $this->_frozen = (bool) ((0x0008 & $options) >> 3);
3951 
3952  // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
3953  $this->_phpSheet->setRightToLeft((bool)((0x0040 & $options) >> 6));
3954 
3955  // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
3956  $isActive = (bool) ((0x0400 & $options) >> 10);
3957  if ($isActive) {
3958  $this->_phpExcel->setActiveSheetIndex($this->_phpExcel->getIndex($this->_phpSheet));
3959  }
3960  }
3961 
3965  private function _readScl()
3966  {
3967  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3968  $recordData = substr($this->_data, $this->_pos + 4, $length);
3969 
3970  // move stream pointer to next record
3971  $this->_pos += 4 + $length;
3972 
3973  // offset: 0; size: 2; numerator of the view magnification
3974  $numerator = self::_GetInt2d($recordData, 0);
3975 
3976  // offset: 2; size: 2; numerator of the view magnification
3977  $denumerator = self::_GetInt2d($recordData, 2);
3978 
3979  // set the zoom scale (in percent)
3980  $this->_phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
3981  }
3982 
3986  private function _readPane()
3987  {
3988  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3989  $recordData = substr($this->_data, $this->_pos + 4, $length);
3990 
3991  // move stream pointer to next record
3992  $this->_pos += 4 + $length;
3993 
3994  if (!$this->_readDataOnly) {
3995  // offset: 0; size: 2; position of vertical split
3996  $px = self::_GetInt2d($recordData, 0);
3997 
3998  // offset: 2; size: 2; position of horizontal split
3999  $py = self::_GetInt2d($recordData, 2);
4000 
4001  if ($this->_frozen) {
4002  // frozen panes
4003  $this->_phpSheet->freezePane(PHPExcel_Cell::stringFromColumnIndex($px) . ($py + 1));
4004  } else {
4005  // unfrozen panes; split windows; not supported by PHPExcel core
4006  }
4007  }
4008  }
4009 
4013  private function _readSelection()
4014  {
4015  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4016  $recordData = substr($this->_data, $this->_pos + 4, $length);
4017 
4018  // move stream pointer to next record
4019  $this->_pos += 4 + $length;
4020 
4021  if (!$this->_readDataOnly) {
4022  // offset: 0; size: 1; pane identifier
4023  $paneId = ord($recordData{0});
4024 
4025  // offset: 1; size: 2; index to row of the active cell
4026  $r = self::_GetInt2d($recordData, 1);
4027 
4028  // offset: 3; size: 2; index to column of the active cell
4029  $c = self::_GetInt2d($recordData, 3);
4030 
4031  // offset: 5; size: 2; index into the following cell range list to the
4032  // entry that contains the active cell
4033  $index = self::_GetInt2d($recordData, 5);
4034 
4035  // offset: 7; size: var; cell range address list containing all selected cell ranges
4036  $data = substr($recordData, 7);
4037  $cellRangeAddressList = $this->_readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4038 
4039  $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4040 
4041  // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4042  if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4043  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4044  }
4045 
4046  // first row '1' + last row '65536' indicates that full column is selected
4047  if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4048  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4049  }
4050 
4051  // first column 'A' + last column 'IV' indicates that full row is selected
4052  if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4053  $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells);
4054  }
4055 
4056  $this->_phpSheet->setSelectedCells($selectedCells);
4057  }
4058  }
4059 
4060  private function _includeCellRangeFiltered($cellRangeAddress)
4061  {
4062  $includeCellRange = true;
4063  if (!is_null($this->getReadFilter())) {
4064  $includeCellRange = false;
4065  $rangeBoundaries = PHPExcel_Cell::getRangeBoundaries($cellRangeAddress);
4066  $rangeBoundaries[1][0]++;
4067  for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; $row++) {
4068  for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; $column++) {
4069  if ($this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle())) {
4070  $includeCellRange = true;
4071  break 2;
4072  }
4073  }
4074  }
4075  }
4076  return $includeCellRange;
4077  }
4078 
4088  private function _readMergedCells()
4089  {
4090  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4091  $recordData = substr($this->_data, $this->_pos + 4, $length);
4092 
4093  // move stream pointer to next record
4094  $this->_pos += 4 + $length;
4095 
4096  if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
4097  $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($recordData);
4098  foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4099  if ($this->_includeCellRangeFiltered($cellRangeAddress)) {
4100  $this->_phpSheet->mergeCells($cellRangeAddress);
4101  }
4102  }
4103  }
4104  }
4105 
4109  private function _readHyperLink()
4110  {
4111  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4112  $recordData = substr($this->_data, $this->_pos + 4, $length);
4113 
4114  // move stream pointer forward to next record
4115  $this->_pos += 4 + $length;
4116 
4117  if (!$this->_readDataOnly) {
4118  // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4119  try {
4120  $cellRange = $this->_readBIFF8CellRangeAddressFixed($recordData, 0, 8);
4121  } catch (Exception $e) {
4122  return;
4123  }
4124 
4125  // offset: 8, size: 16; GUID of StdLink
4126 
4127  // offset: 24, size: 4; unknown value
4128 
4129  // offset: 28, size: 4; option flags
4130 
4131  // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4132  $isFileLinkOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 0;
4133 
4134  // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4135  $isAbsPathOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 1;
4136 
4137  // bit: 2 (and 4); mask: 0x00000014; 0 = no description
4138  $hasDesc = (0x00000014 & self::_GetInt2d($recordData, 28)) >> 2;
4139 
4140  // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4141  $hasText = (0x00000008 & self::_GetInt2d($recordData, 28)) >> 3;
4142 
4143  // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4144  $hasFrame = (0x00000080 & self::_GetInt2d($recordData, 28)) >> 7;
4145 
4146  // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4147  $isUNC = (0x00000100 & self::_GetInt2d($recordData, 28)) >> 8;
4148 
4149  // offset within record data
4150  $offset = 32;
4151 
4152  if ($hasDesc) {
4153  // offset: 32; size: var; character count of description text
4154  $dl = self::_GetInt4d($recordData, 32);
4155  // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4156  $desc = self::_encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4157  $offset += 4 + 2 * $dl;
4158  }
4159  if ($hasFrame) {
4160  $fl = self::_GetInt4d($recordData, $offset);
4161  $offset += 4 + 2 * $fl;
4162  }
4163 
4164  // detect type of hyperlink (there are 4 types)
4165  $hyperlinkType = null;
4166 
4167  if ($isUNC) {
4168  $hyperlinkType = 'UNC';
4169  } else if (!$isFileLinkOrUrl) {
4170  $hyperlinkType = 'workbook';
4171  } else if (ord($recordData{$offset}) == 0x03) {
4172  $hyperlinkType = 'local';
4173  } else if (ord($recordData{$offset}) == 0xE0) {
4174  $hyperlinkType = 'URL';
4175  }
4176 
4177  switch ($hyperlinkType) {
4178  case 'URL':
4179  // section 5.58.2: Hyperlink containing a URL
4180  // e.g. http://example.org/index.php
4181 
4182  // offset: var; size: 16; GUID of URL Moniker
4183  $offset += 16;
4184  // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4185  $us = self::_GetInt4d($recordData, $offset);
4186  $offset += 4;
4187  // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4188  $url = self::_encodeUTF16(substr($recordData, $offset, $us - 2), false);
4189  $url .= $hasText ? '#' : '';
4190  $offset += $us;
4191  break;
4192 
4193  case 'local':
4194  // section 5.58.3: Hyperlink to local file
4195  // examples:
4196  // mydoc.txt
4197  // ../../somedoc.xls#Sheet!A1
4198 
4199  // offset: var; size: 16; GUI of File Moniker
4200  $offset += 16;
4201 
4202  // offset: var; size: 2; directory up-level count.
4203  $upLevelCount = self::_GetInt2d($recordData, $offset);
4204  $offset += 2;
4205 
4206  // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4207  $sl = self::_GetInt4d($recordData, $offset);
4208  $offset += 4;
4209 
4210  // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4211  $shortenedFilePath = substr($recordData, $offset, $sl);
4212  $shortenedFilePath = self::_encodeUTF16($shortenedFilePath, true);
4213  $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4214 
4215  $offset += $sl;
4216 
4217  // offset: var; size: 24; unknown sequence
4218  $offset += 24;
4219 
4220  // extended file path
4221  // offset: var; size: 4; size of the following file link field including string lenth mark
4222  $sz = self::_GetInt4d($recordData, $offset);
4223  $offset += 4;
4224 
4225  // only present if $sz > 0
4226  if ($sz > 0) {
4227  // offset: var; size: 4; size of the character array of the extended file path and name
4228  $xl = self::_GetInt4d($recordData, $offset);
4229  $offset += 4;
4230 
4231  // offset: var; size 2; unknown
4232  $offset += 2;
4233 
4234  // offset: var; size $xl; character array of the extended file path and name.
4235  $extendedFilePath = substr($recordData, $offset, $xl);
4236  $extendedFilePath = self::_encodeUTF16($extendedFilePath, false);
4237  $offset += $xl;
4238  }
4239 
4240  // construct the path
4241  $url = str_repeat('..\\', $upLevelCount);
4242  $url .= ($sz > 0) ?
4243  $extendedFilePath : $shortenedFilePath; // use extended path if available
4244  $url .= $hasText ? '#' : '';
4245 
4246  break;
4247 
4248 
4249  case 'UNC':
4250  // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4251  // todo: implement
4252  return;
4253 
4254  case 'workbook':
4255  // section 5.58.5: Hyperlink to the Current Workbook
4256  // e.g. Sheet2!B1:C2, stored in text mark field
4257  $url = 'sheet://';
4258  break;
4259 
4260  default:
4261  return;
4262 
4263  }
4264 
4265  if ($hasText) {
4266  // offset: var; size: 4; character count of text mark including trailing zero word
4267  $tl = self::_GetInt4d($recordData, $offset);
4268  $offset += 4;
4269  // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4270  $text = self::_encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4271  $url .= $text;
4272  }
4273 
4274  // apply the hyperlink to all the relevant cells
4275  foreach (PHPExcel_Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4276  $this->_phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4277  }
4278  }
4279  }
4280 
4284  private function _readDataValidations()
4285  {
4286  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4287  $recordData = substr($this->_data, $this->_pos + 4, $length);
4288 
4289  // move stream pointer forward to next record
4290  $this->_pos += 4 + $length;
4291  }
4292 
4296  private function _readDataValidation()
4297  {
4298  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4299  $recordData = substr($this->_data, $this->_pos + 4, $length);
4300 
4301  // move stream pointer forward to next record
4302  $this->_pos += 4 + $length;
4303 
4304  if ($this->_readDataOnly) {
4305  return;
4306  }
4307 
4308  // offset: 0; size: 4; Options
4309  $options = self::_GetInt4d($recordData, 0);
4310 
4311  // bit: 0-3; mask: 0x0000000F; type
4312  $type = (0x0000000F & $options) >> 0;
4313  switch ($type) {
4314  case 0x00: $type = PHPExcel_Cell_DataValidation::TYPE_NONE; break;
4315  case 0x01: $type = PHPExcel_Cell_DataValidation::TYPE_WHOLE; break;
4317  case 0x03: $type = PHPExcel_Cell_DataValidation::TYPE_LIST; break;
4318  case 0x04: $type = PHPExcel_Cell_DataValidation::TYPE_DATE; break;
4319  case 0x05: $type = PHPExcel_Cell_DataValidation::TYPE_TIME; break;
4322  }
4323 
4324  // bit: 4-6; mask: 0x00000070; error type
4325  $errorStyle = (0x00000070 & $options) >> 4;
4326  switch ($errorStyle) {
4327  case 0x00: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_STOP; break;
4328  case 0x01: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_WARNING; break;
4329  case 0x02: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_INFORMATION; break;
4330  }
4331 
4332  // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4333  // I have only seen cases where this is 1
4334  $explicitFormula = (0x00000080 & $options) >> 7;
4335 
4336  // bit: 8; mask: 0x00000100; 1= empty cells allowed
4337  $allowBlank = (0x00000100 & $options) >> 8;
4338 
4339  // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4340  $suppressDropDown = (0x00000200 & $options) >> 9;
4341 
4342  // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4343  $showInputMessage = (0x00040000 & $options) >> 18;
4344 
4345  // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4346  $showErrorMessage = (0x00080000 & $options) >> 19;
4347 
4348  // bit: 20-23; mask: 0x00F00000; condition operator
4349  $operator = (0x00F00000 & $options) >> 20;
4350  switch ($operator) {
4351  case 0x00: $operator = PHPExcel_Cell_DataValidation::OPERATOR_BETWEEN ; break;
4352  case 0x01: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTBETWEEN ; break;
4353  case 0x02: $operator = PHPExcel_Cell_DataValidation::OPERATOR_EQUAL ; break;
4354  case 0x03: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTEQUAL ; break;
4355  case 0x04: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHAN ; break;
4356  case 0x05: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHAN ; break;
4357  case 0x06: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHANOREQUAL; break;
4358  case 0x07: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHANOREQUAL ; break;
4359  }
4360 
4361  // offset: 4; size: var; title of the prompt box
4362  $offset = 4;
4363  $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4364  $promptTitle = $string['value'] !== chr(0) ?
4365  $string['value'] : '';
4366  $offset += $string['size'];
4367 
4368  // offset: var; size: var; title of the error box
4369  $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4370  $errorTitle = $string['value'] !== chr(0) ?
4371  $string['value'] : '';
4372  $offset += $string['size'];
4373 
4374  // offset: var; size: var; text of the prompt box
4375  $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4376  $prompt = $string['value'] !== chr(0) ?
4377  $string['value'] : '';
4378  $offset += $string['size'];
4379 
4380  // offset: var; size: var; text of the error box
4381  $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4382  $error = $string['value'] !== chr(0) ?
4383  $string['value'] : '';
4384  $offset += $string['size'];
4385 
4386  // offset: var; size: 2; size of the formula data for the first condition
4387  $sz1 = self::_GetInt2d($recordData, $offset);
4388  $offset += 2;
4389 
4390  // offset: var; size: 2; not used
4391  $offset += 2;
4392 
4393  // offset: var; size: $sz1; formula data for first condition (without size field)
4394  $formula1 = substr($recordData, $offset, $sz1);
4395  $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4396  try {
4397  $formula1 = $this->_getFormulaFromStructure($formula1);
4398 
4399  // in list type validity, null characters are used as item separators
4401  $formula1 = str_replace(chr(0), ',', $formula1);
4402  }
4403  } catch (Exception $e) {
4404  return;
4405  }
4406  $offset += $sz1;
4407 
4408  // offset: var; size: 2; size of the formula data for the first condition
4409  $sz2 = self::_GetInt2d($recordData, $offset);
4410  $offset += 2;
4411 
4412  // offset: var; size: 2; not used
4413  $offset += 2;
4414 
4415  // offset: var; size: $sz2; formula data for second condition (without size field)
4416  $formula2 = substr($recordData, $offset, $sz2);
4417  $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4418  try {
4419  $formula2 = $this->_getFormulaFromStructure($formula2);
4420  } catch (Exception $e) {
4421  return;
4422  }
4423  $offset += $sz2;
4424 
4425  // offset: var; size: var; cell range address list with
4426  $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList(substr($recordData, $offset));
4427  $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4428 
4429  foreach ($cellRangeAddresses as $cellRange) {
4430  $stRange = $this->_phpSheet->shrinkRangeToFit($cellRange);
4432  foreach ($stRange as $coordinate) {
4433  $objValidation = $this->_phpSheet->getCell($coordinate)->getDataValidation();
4434  $objValidation->setType($type);
4435  $objValidation->setErrorStyle($errorStyle);
4436  $objValidation->setAllowBlank((bool)$allowBlank);
4437  $objValidation->setShowInputMessage((bool)$showInputMessage);
4438  $objValidation->setShowErrorMessage((bool)$showErrorMessage);
4439  $objValidation->setShowDropDown(!$suppressDropDown);
4440  $objValidation->setOperator($operator);
4441  $objValidation->setErrorTitle($errorTitle);
4442  $objValidation->setError($error);
4443  $objValidation->setPromptTitle($promptTitle);
4444  $objValidation->setPrompt($prompt);
4445  $objValidation->setFormula1($formula1);
4446  $objValidation->setFormula2($formula2);
4447  }
4448  }
4449 
4450  }
4451 
4455  private function _readSheetLayout()
4456  {
4457  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4458  $recordData = substr($this->_data, $this->_pos + 4, $length);
4459 
4460  // move stream pointer to next record
4461  $this->_pos += 4 + $length;
4462 
4463  // local pointer in record data
4464  $offset = 0;
4465 
4466  if (!$this->_readDataOnly) {
4467  // offset: 0; size: 2; repeated record identifier 0x0862
4468 
4469  // offset: 2; size: 10; not used
4470 
4471  // offset: 12; size: 4; size of record data
4472  // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
4473  $sz = self::_GetInt4d($recordData, 12);
4474 
4475  switch ($sz) {
4476  case 0x14:
4477  // offset: 16; size: 2; color index for sheet tab
4478  $colorIndex = self::_GetInt2d($recordData, 16);
4479  $color = self::_readColor($colorIndex,$this->_palette,$this->_version);
4480  $this->_phpSheet->getTabColor()->setRGB($color['rgb']);
4481  break;
4482 
4483  case 0x28:
4484  // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
4485  return;
4486  break;
4487  }
4488  }
4489  }
4490 
4494  private function _readSheetProtection()
4495  {
4496  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4497  $recordData = substr($this->_data, $this->_pos + 4, $length);
4498 
4499  // move stream pointer to next record
4500  $this->_pos += 4 + $length;
4501 
4502  if ($this->_readDataOnly) {
4503  return;
4504  }
4505 
4506  // offset: 0; size: 2; repeated record header
4507 
4508  // offset: 2; size: 2; FRT cell reference flag (=0 currently)
4509 
4510  // offset: 4; size: 8; Currently not used and set to 0
4511 
4512  // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
4513  $isf = self::_GetInt2d($recordData, 12);
4514  if ($isf != 2) {
4515  return;
4516  }
4517 
4518  // offset: 14; size: 1; =1 since this is a feat header
4519 
4520  // offset: 15; size: 4; size of rgbHdrSData
4521 
4522  // rgbHdrSData, assume "Enhanced Protection"
4523  // offset: 19; size: 2; option flags
4524  $options = self::_GetInt2d($recordData, 19);
4525 
4526  // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
4527  $bool = (0x0001 & $options) >> 0;
4528  $this->_phpSheet->getProtection()->setObjects(!$bool);
4529 
4530  // bit: 1; mask 0x0002; edit scenarios
4531  $bool = (0x0002 & $options) >> 1;
4532  $this->_phpSheet->getProtection()->setScenarios(!$bool);
4533 
4534  // bit: 2; mask 0x0004; format cells
4535  $bool = (0x0004 & $options) >> 2;
4536  $this->_phpSheet->getProtection()->setFormatCells(!$bool);
4537 
4538  // bit: 3; mask 0x0008; format columns
4539  $bool = (0x0008 & $options) >> 3;
4540  $this->_phpSheet->getProtection()->setFormatColumns(!$bool);
4541 
4542  // bit: 4; mask 0x0010; format rows
4543  $bool = (0x0010 & $options) >> 4;
4544  $this->_phpSheet->getProtection()->setFormatRows(!$bool);
4545 
4546  // bit: 5; mask 0x0020; insert columns
4547  $bool = (0x0020 & $options) >> 5;
4548  $this->_phpSheet->getProtection()->setInsertColumns(!$bool);
4549 
4550  // bit: 6; mask 0x0040; insert rows
4551  $bool = (0x0040 & $options) >> 6;
4552  $this->_phpSheet->getProtection()->setInsertRows(!$bool);
4553 
4554  // bit: 7; mask 0x0080; insert hyperlinks
4555  $bool = (0x0080 & $options) >> 7;
4556  $this->_phpSheet->getProtection()->setInsertHyperlinks(!$bool);
4557 
4558  // bit: 8; mask 0x0100; delete columns
4559  $bool = (0x0100 & $options) >> 8;
4560  $this->_phpSheet->getProtection()->setDeleteColumns(!$bool);
4561 
4562  // bit: 9; mask 0x0200; delete rows
4563  $bool = (0x0200 & $options) >> 9;
4564  $this->_phpSheet->getProtection()->setDeleteRows(!$bool);
4565 
4566  // bit: 10; mask 0x0400; select locked cells
4567  $bool = (0x0400 & $options) >> 10;
4568  $this->_phpSheet->getProtection()->setSelectLockedCells(!$bool);
4569 
4570  // bit: 11; mask 0x0800; sort cell range
4571  $bool = (0x0800 & $options) >> 11;
4572  $this->_phpSheet->getProtection()->setSort(!$bool);
4573 
4574  // bit: 12; mask 0x1000; auto filter
4575  $bool = (0x1000 & $options) >> 12;
4576  $this->_phpSheet->getProtection()->setAutoFilter(!$bool);
4577 
4578  // bit: 13; mask 0x2000; pivot tables
4579  $bool = (0x2000 & $options) >> 13;
4580  $this->_phpSheet->getProtection()->setPivotTables(!$bool);
4581 
4582  // bit: 14; mask 0x4000; select unlocked cells
4583  $bool = (0x4000 & $options) >> 14;
4584  $this->_phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
4585 
4586  // offset: 21; size: 2; not used
4587  }
4588 
4594  private function _readRangeProtection()
4595  {
4596  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4597  $recordData = substr($this->_data, $this->_pos + 4, $length);
4598 
4599  // move stream pointer to next record
4600  $this->_pos += 4 + $length;
4601 
4602  // local pointer in record data
4603  $offset = 0;
4604 
4605  if (!$this->_readDataOnly) {
4606  $offset += 12;
4607 
4608  // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
4609  $isf = self::_GetInt2d($recordData, 12);
4610  if ($isf != 2) {
4611  // we only read FEAT records of type 2
4612  return;
4613  }
4614  $offset += 2;
4615 
4616  $offset += 5;
4617 
4618  // offset: 19; size: 2; count of ref ranges this feature is on
4619  $cref = self::_GetInt2d($recordData, 19);
4620  $offset += 2;
4621 
4622  $offset += 6;
4623 
4624  // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
4625  $cellRanges = array();
4626  for ($i = 0; $i < $cref; ++$i) {
4627  try {
4628  $cellRange = $this->_readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
4629  } catch (Exception $e) {
4630  return;
4631  }
4632  $cellRanges[] = $cellRange;
4633  $offset += 8;
4634  }
4635 
4636  // offset: var; size: var; variable length of feature specific data
4637  $rgbFeat = substr($recordData, $offset);
4638  $offset += 4;
4639 
4640  // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
4641  $wPassword = self::_GetInt4d($recordData, $offset);
4642  $offset += 4;
4643 
4644  // Apply range protection to sheet
4645  if ($cellRanges) {
4646  $this->_phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
4647  }
4648  }
4649  }
4650 
4654  private function _readImData()
4655  {
4656  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4657 
4658  // get spliced record data
4659  $splicedRecordData = $this->_getSplicedRecordData();
4660  $recordData = $splicedRecordData['recordData'];
4661 
4662  // UNDER CONSTRUCTION
4663 
4664  // offset: 0; size: 2; image format
4665  $cf = self::_GetInt2d($recordData, 0);
4666 
4667  // offset: 2; size: 2; environment from which the file was written
4668  $env = self::_GetInt2d($recordData, 2);
4669 
4670  // offset: 4; size: 4; length of the image data
4671  $lcb = self::_GetInt4d($recordData, 4);
4672 
4673  // offset: 8; size: var; image data
4674  $iData = substr($recordData, 8);
4675 
4676  switch ($cf) {
4677  case 0x09: // Windows bitmap format
4678  // BITMAPCOREINFO
4679  // 1. BITMAPCOREHEADER
4680  // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure
4681  $bcSize = self::_GetInt4d($iData, 0);
4682 // var_dump($bcSize);
4683 
4684  // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels
4685  $bcWidth = self::_GetInt2d($iData, 4);
4686 // var_dump($bcWidth);
4687 
4688  // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.
4689  $bcHeight = self::_GetInt2d($iData, 6);
4690 // var_dump($bcHeight);
4691  $ih = imagecreatetruecolor($bcWidth, $bcHeight);
4692 
4693  // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1
4694 
4695  // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24
4696  $bcBitCount = self::_GetInt2d($iData, 10);
4697 // var_dump($bcBitCount);
4698 
4699  $rgbString = substr($iData, 12);
4700  $rgbTriples = array();
4701  while (strlen($rgbString) > 0) {
4702  $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
4703  $rgbString = substr($rgbString, 3);
4704  }
4705  $x = 0;
4706  $y = 0;
4707  foreach ($rgbTriples as $i => $rgbTriple) {
4708  $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
4709  imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
4710  $x = ($x + 1) % $bcWidth;
4711  $y = $y + floor(($x + 1) / $bcWidth);
4712  }
4713  //imagepng($ih, 'image.png');
4714 
4715  $drawing = new PHPExcel_Worksheet_Drawing();
4716  $drawing->setPath($filename);
4717  $drawing->setWorksheet($this->_phpSheet);
4718 
4719  break;
4720 
4721  case 0x02: // Windows metafile or Macintosh PICT format
4722  case 0x0e: // native format
4723  default;
4724  break;
4725 
4726  }
4727 
4728  // _getSplicedRecordData() takes care of moving current position in data stream
4729  }
4730 
4736  private function _readContinue()
4737  {
4738  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4739  $recordData = substr($this->_data, $this->_pos + 4, $length);
4740 
4741  // check if we are reading drawing data
4742  // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
4743  if ($this->_drawingData == '') {
4744  // move stream pointer to next record
4745  $this->_pos += 4 + $length;
4746 
4747  return;
4748  }
4749 
4750  // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
4751  if ($length < 4) {
4752  // move stream pointer to next record
4753  $this->_pos += 4 + $length;
4754 
4755  return;
4756  }
4757 
4758  // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
4759  // look inside CONTINUE record to see if it looks like a part of an Escher stream
4760  // we know that Escher stream may be split at least at
4761  // 0xF003 MsofbtSpgrContainer
4762  // 0xF004 MsofbtSpContainer
4763  // 0xF00D MsofbtClientTextbox
4764  $validSplitPoints = array(0xF003, 0xF004, 0xF00D); // add identifiers if we find more
4765 
4766  $splitPoint = self::_GetInt2d($recordData, 2);
4767  if (in_array($splitPoint, $validSplitPoints)) {
4768  // get spliced record data (and move pointer to next record)
4769  $splicedRecordData = $this->_getSplicedRecordData();
4770  $this->_drawingData .= $splicedRecordData['recordData'];
4771 
4772  return;
4773  }
4774 
4775  // move stream pointer to next record
4776  $this->_pos += 4 + $length;
4777 
4778  }
4779 
4780 
4789  private function _getSplicedRecordData()
4790  {
4791  $data = '';
4792  $spliceOffsets = array();
4793 
4794  $i = 0;
4795  $spliceOffsets[0] = 0;
4796 
4797  do {
4798  ++$i;
4799 
4800  // offset: 0; size: 2; identifier
4801  $identifier = self::_GetInt2d($this->_data, $this->_pos);
4802  // offset: 2; size: 2; length
4803  $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4804  $data .= substr($this->_data, $this->_pos + 4, $length);
4805 
4806  $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
4807 
4808  $this->_pos += 4 + $length;
4809  $nextIdentifier = self::_GetInt2d($this->_data, $this->_pos);
4810  }
4811  while ($nextIdentifier == self::XLS_Type_CONTINUE);
4812 
4813  $splicedData = array(
4814  'recordData' => $data,
4815  'spliceOffsets' => $spliceOffsets,
4816  );
4817 
4818  return $splicedData;
4819 
4820  }
4821 
4829  private function _getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
4830  {
4831  // offset: 0; size: 2; size of the following formula data
4832  $sz = self::_GetInt2d($formulaStructure, 0);
4833 
4834  // offset: 2; size: sz
4835  $formulaData = substr($formulaStructure, 2, $sz);
4836 
4837  // for debug: dump the formula data
4838  //echo '<xmp>';
4839  //echo 'size: ' . $sz . "\n";
4840  //echo 'the entire formula data: ';
4841  //Debug::dump($formulaData);
4842  //echo "\n----\n";
4843 
4844  // offset: 2 + sz; size: variable (optional)
4845  if (strlen($formulaStructure) > 2 + $sz) {
4846  $additionalData = substr($formulaStructure, 2 + $sz);
4847 
4848  // for debug: dump the additional data
4849  //echo 'the entire additional data: ';
4850  //Debug::dump($additionalData);
4851  //echo "\n----\n";
4852 
4853  } else {
4854  $additionalData = '';
4855  }
4856 
4857  return $this->_getFormulaFromData($formulaData, $additionalData, $baseCell);
4858  }
4859 
4868  private function _getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
4869  {
4870  // start parsing the formula data
4871  $tokens = array();
4872 
4873  while (strlen($formulaData) > 0 and $token = $this->_getNextToken($formulaData, $baseCell)) {
4874  $tokens[] = $token;
4875  $formulaData = substr($formulaData, $token['size']);
4876 
4877  // for debug: dump the token
4878  //var_dump($token);
4879  }
4880 
4881  $formulaString = $this->_createFormulaFromTokens($tokens, $additionalData);
4882 
4883  return $formulaString;
4884  }
4885 
4894  private function _createFormulaFromTokens($tokens, $additionalData)
4895  {
4896  // empty formula?
4897  if (count($tokens) == 0) {
4898  return '';
4899  }
4900 
4901  $formulaStrings = array();
4902  foreach ($tokens as $token) {
4903  // initialize spaces
4904  $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
4905  $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
4906  $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
4907  $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
4908  $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
4909  $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
4910 
4911  switch ($token['name']) {
4912  case 'tAdd': // addition
4913  case 'tConcat': // addition
4914  case 'tDiv': // division
4915  case 'tEQ': // equality
4916  case 'tGE': // greater than or equal
4917  case 'tGT': // greater than
4918  case 'tIsect': // intersection
4919  case 'tLE': // less than or equal
4920  case 'tList': // less than or equal
4921  case 'tLT': // less than
4922  case 'tMul': // multiplication
4923  case 'tNE': // multiplication
4924  case 'tPower': // power
4925  case 'tRange': // range
4926  case 'tSub': // subtraction
4927  $op2 = array_pop($formulaStrings);
4928  $op1 = array_pop($formulaStrings);
4929  $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
4930  unset($space0, $space1);
4931  break;
4932  case 'tUplus': // unary plus
4933  case 'tUminus': // unary minus
4934  $op = array_pop($formulaStrings);
4935  $formulaStrings[] = "$space1$space0{$token['data']}$op";
4936  unset($space0, $space1);
4937  break;
4938  case 'tPercent': // percent sign
4939  $op = array_pop($formulaStrings);
4940  $formulaStrings[] = "$op$space1$space0{$token['data']}";
4941  unset($space0, $space1);
4942  break;
4943  case 'tAttrVolatile': // indicates volatile function
4944  case 'tAttrIf':
4945  case 'tAttrSkip':
4946  case 'tAttrChoose':
4947  // token is only important for Excel formula evaluator
4948  // do nothing
4949  break;
4950  case 'tAttrSpace': // space / carriage return
4951  // space will be used when next token arrives, do not alter formulaString stack
4952  switch ($token['data']['spacetype']) {
4953  case 'type0':
4954  $space0 = str_repeat(' ', $token['data']['spacecount']);
4955  break;
4956  case 'type1':
4957  $space1 = str_repeat("\n", $token['data']['spacecount']);
4958  break;
4959  case 'type2':
4960  $space2 = str_repeat(' ', $token['data']['spacecount']);
4961  break;
4962  case 'type3':
4963  $space3 = str_repeat("\n", $token['data']['spacecount']);
4964  break;
4965  case 'type4':
4966  $space4 = str_repeat(' ', $token['data']['spacecount']);
4967  break;
4968  case 'type5':
4969  $space5 = str_repeat("\n", $token['data']['spacecount']);
4970  break;
4971  }
4972  break;
4973  case 'tAttrSum': // SUM function with one parameter
4974  $op = array_pop($formulaStrings);
4975  $formulaStrings[] = "{$space1}{$space0}SUM($op)";
4976  unset($space0, $space1);
4977  break;
4978  case 'tFunc': // function with fixed number of arguments
4979  case 'tFuncV': // function with variable number of arguments
4980  if ($token['data']['function'] != '') {
4981  // normal function
4982  $ops = array(); // array of operators
4983  for ($i = 0; $i < $token['data']['args']; ++$i) {
4984  $ops[] = array_pop($formulaStrings);
4985  }
4986  $ops = array_reverse($ops);
4987  $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ")";
4988  unset($space0, $space1);
4989  } else {
4990  // add-in function
4991  $ops = array(); // array of operators
4992  for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
4993  $ops[] = array_pop($formulaStrings);
4994  }
4995  $ops = array_reverse($ops);
4996  $function = array_pop($formulaStrings);
4997  $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ")";
4998  unset($space0, $space1);
4999  }
5000  break;
5001  case 'tParen': // parenthesis
5002  $expression = array_pop($formulaStrings);
5003  $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5004  unset($space2, $space3, $space4, $space5);
5005  break;
5006  case 'tArray': // array constant
5007  $constantArray = self::_readBIFF8ConstantArray($additionalData);
5008  $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5009  $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5010  unset($space0, $space1);
5011  break;
5012  case 'tMemArea':
5013  // bite off chunk of additional data
5014  $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($additionalData);
5015  $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5016  $formulaStrings[] = "$space1$space0{$token['data']}";
5017  unset($space0, $space1);
5018  break;
5019  case 'tArea': // cell range address
5020  case 'tBool': // boolean
5021  case 'tErr': // error code
5022  case 'tInt': // integer
5023  case 'tMemErr':
5024  case 'tMemFunc':
5025  case 'tMissArg':
5026  case 'tName':
5027  case 'tNameX':
5028  case 'tNum': // number
5029  case 'tRef': // single cell reference
5030  case 'tRef3d': // 3d cell reference
5031  case 'tArea3d': // 3d cell range reference
5032  case 'tRefN':
5033  case 'tAreaN':
5034  case 'tStr': // string
5035  $formulaStrings[] = "$space1$space0{$token['data']}";
5036  unset($space0, $space1);
5037  break;
5038  }
5039  }
5040  $formulaString = $formulaStrings[0];
5041 
5042  // for debug: dump the human readable formula
5043  //echo '----' . "\n";
5044  //echo 'Formula: ' . $formulaString;
5045 
5046  return $formulaString;
5047  }
5048 
5057  private function _getNextToken($formulaData, $baseCell = 'A1')
5058  {
5059  // offset: 0; size: 1; token id
5060  $id = ord($formulaData[0]); // token id
5061  $name = false; // initialize token name
5062 
5063  switch ($id) {
5064  case 0x03: $name = 'tAdd'; $size = 1; $data = '+'; break;
5065  case 0x04: $name = 'tSub'; $size = 1; $data = '-'; break;
5066  case 0x05: $name = 'tMul'; $size = 1; $data = '*'; break;
5067  case 0x06: $name = 'tDiv'; $size = 1; $data = '/'; break;
5068  case 0x07: $name = 'tPower'; $size = 1; $data = '^'; break;
5069  case 0x08: $name = 'tConcat'; $size = 1; $data = '&'; break;
5070  case 0x09: $name = 'tLT'; $size = 1; $data = '<'; break;
5071  case 0x0A: $name = 'tLE'; $size = 1; $data = '<='; break;
5072  case 0x0B: $name = 'tEQ'; $size = 1; $data = '='; break;
5073  case 0x0C: $name = 'tGE'; $size = 1; $data = '>='; break;
5074  case 0x0D: $name = 'tGT'; $size = 1; $data = '>'; break;
5075  case 0x0E: $name = 'tNE'; $size = 1; $data = '<>'; break;
5076  case 0x0F: $name = 'tIsect'; $size = 1; $data = ' '; break;
5077  case 0x10: $name = 'tList'; $size = 1; $data = ','; break;
5078  case 0x11: $name = 'tRange'; $size = 1; $data = ':'; break;
5079  case 0x12: $name = 'tUplus'; $size = 1; $data = '+'; break;
5080  case 0x13: $name = 'tUminus'; $size = 1; $data = '-'; break;
5081  case 0x14: $name = 'tPercent'; $size = 1; $data = '%'; break;
5082  case 0x15: // parenthesis
5083  $name = 'tParen';
5084  $size = 1;
5085  $data = null;
5086  break;
5087  case 0x16: // missing argument
5088  $name = 'tMissArg';
5089  $size = 1;
5090  $data = '';
5091  break;
5092  case 0x17: // string
5093  $name = 'tStr';
5094  // offset: 1; size: var; Unicode string, 8-bit string length
5095  $string = self::_readUnicodeStringShort(substr($formulaData, 1));
5096  $size = 1 + $string['size'];
5097  $data = self::_UTF8toExcelDoubleQuoted($string['value']);
5098  break;
5099  case 0x19: // Special attribute
5100  // offset: 1; size: 1; attribute type flags:
5101  switch (ord($formulaData[1])) {
5102  case 0x01:
5103  $name = 'tAttrVolatile';
5104  $size = 4;
5105  $data = null;
5106  break;
5107  case 0x02:
5108  $name = 'tAttrIf';
5109  $size = 4;
5110  $data = null;
5111  break;
5112  case 0x04:
5113  $name = 'tAttrChoose';
5114  // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5115  $nc = self::_GetInt2d($formulaData, 2);
5116  // offset: 4; size: 2 * $nc
5117  // offset: 4 + 2 * $nc; size: 2
5118  $size = 2 * $nc + 6;
5119  $data = null;
5120  break;
5121  case 0x08:
5122  $name = 'tAttrSkip';
5123  $size = 4;
5124  $data = null;
5125  break;
5126  case 0x10:
5127  $name = 'tAttrSum';
5128  $size = 4;
5129  $data = null;
5130  break;
5131  case 0x40:
5132  case 0x41:
5133  $name = 'tAttrSpace';
5134  $size = 4;
5135  // offset: 2; size: 2; space type and position
5136  switch (ord($formulaData[2])) {
5137  case 0x00:
5138  $spacetype = 'type0';
5139  break;
5140  case 0x01:
5141  $spacetype = 'type1';
5142  break;
5143  case 0x02:
5144  $spacetype = 'type2';
5145  break;
5146  case 0x03:
5147  $spacetype = 'type3';
5148  break;
5149  case 0x04:
5150  $spacetype = 'type4';
5151  break;
5152  case 0x05:
5153  $spacetype = 'type5';
5154  break;
5155  default:
5156  throw new Exception('Unrecognized space type in tAttrSpace token');
5157  break;
5158  }
5159  // offset: 3; size: 1; number of inserted spaces/carriage returns
5160  $spacecount = ord($formulaData[3]);
5161 
5162  $data = array('spacetype' => $spacetype, 'spacecount' => $spacecount);
5163  break;
5164  default:
5165  throw new Exception('Unrecognized attribute flag in tAttr token');
5166  break;
5167  }
5168  break;
5169  case 0x1C: // error code
5170  // offset: 1; size: 1; error code
5171  $name = 'tErr';
5172  $size = 2;
5173  $data = self::_mapErrorCode(ord($formulaData[1]));
5174  break;
5175  case 0x1D: // boolean
5176  // offset: 1; size: 1; 0 = false, 1 = true;
5177  $name = 'tBool';
5178  $size = 2;
5179  $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5180  break;
5181  case 0x1E: // integer
5182  // offset: 1; size: 2; unsigned 16-bit integer
5183  $name = 'tInt';
5184  $size = 3;
5185  $data = self::_GetInt2d($formulaData, 1);
5186  break;
5187  case 0x1F: // number
5188  // offset: 1; size: 8;
5189  $name = 'tNum';
5190  $size = 9;
5191  $data = self::_extractNumber(substr($formulaData, 1));
5192  $data = str_replace(',', '.', (string)$data); // in case non-English locale
5193  break;
5194  case 0x20: // array constant
5195  case 0x40:
5196  case 0x60:
5197  // offset: 1; size: 7; not used
5198  $name = 'tArray';
5199  $size = 8;
5200  $data = null;
5201  break;
5202  case 0x21: // function with fixed number of arguments
5203  case 0x41:
5204  case 0x61:
5205  $name = 'tFunc';
5206  $size = 3;
5207  // offset: 1; size: 2; index to built-in sheet function
5208  switch (self::_GetInt2d($formulaData, 1)) {
5209  case 2: $function = 'ISNA'; $args = 1; break;
5210  case 3: $function = 'ISERROR'; $args = 1; break;
5211  case 10: $function = 'NA'; $args = 0; break;
5212  case 15: $function = 'SIN'; $args = 1; break;
5213  case 16: $function = 'COS'; $args = 1; break;
5214  case 17: $function = 'TAN'; $args = 1; break;
5215  case 18: $function = 'ATAN'; $args = 1; break;
5216  case 19: $function = 'PI'; $args = 0; break;
5217  case 20: $function = 'SQRT'; $args = 1; break;
5218  case 21: $function = 'EXP'; $args = 1; break;
5219  case 22: $function = 'LN'; $args = 1; break;
5220  case 23: $function = 'LOG10'; $args = 1; break;
5221  case 24: $function = 'ABS'; $args = 1; break;
5222  case 25: $function = 'INT'; $args = 1; break;
5223  case 26: $function = 'SIGN'; $args = 1; break;
5224  case 27: $function = 'ROUND'; $args = 2; break;
5225  case 30: $function = 'REPT'; $args = 2; break;
5226  case 31: $function = 'MID'; $args = 3; break;
5227  case 32: $function = 'LEN'; $args = 1; break;
5228  case 33: $function = 'VALUE'; $args = 1; break;
5229  case 34: $function = 'TRUE'; $args = 0; break;
5230  case 35: $function = 'FALSE'; $args = 0; break;
5231  case 38: $function = 'NOT'; $args = 1; break;
5232  case 39: $function = 'MOD'; $args = 2; break;
5233  case 40: $function = 'DCOUNT'; $args = 3; break;
5234  case 41: $function = 'DSUM'; $args = 3; break;
5235  case 42: $function = 'DAVERAGE'; $args = 3; break;
5236  case 43: $function = 'DMIN'; $args = 3; break;
5237  case 44: $function = 'DMAX'; $args = 3; break;
5238  case 45: $function = 'DSTDEV'; $args = 3; break;
5239  case 48: $function = 'TEXT'; $args = 2; break;
5240  case 61: $function = 'MIRR'; $args = 3; break;
5241  case 63: $function = 'RAND'; $args = 0; break;
5242  case 65: $function = 'DATE'; $args = 3; break;
5243  case 66: $function = 'TIME'; $args = 3; break;
5244  case 67: $function = 'DAY'; $args = 1; break;
5245  case 68: $function = 'MONTH'; $args = 1; break;
5246  case 69: $function = 'YEAR'; $args = 1; break;
5247  case 71: $function = 'HOUR'; $args = 1; break;
5248  case 72: $function = 'MINUTE'; $args = 1; break;
5249  case 73: $function = 'SECOND'; $args = 1; break;
5250  case 74: $function = 'NOW'; $args = 0; break;
5251  case 75: $function = 'AREAS'; $args = 1; break;
5252  case 76: $function = 'ROWS'; $args = 1; break;
5253  case 77: $function = 'COLUMNS'; $args = 1; break;
5254  case 83: $function = 'TRANSPOSE'; $args = 1; break;
5255  case 86: $function = 'TYPE'; $args = 1; break;
5256  case 97: $function = 'ATAN2'; $args = 2; break;
5257  case 98: $function = 'ASIN'; $args = 1; break;
5258  case 99: $function = 'ACOS'; $args = 1; break;
5259  case 105: $function = 'ISREF'; $args = 1; break;
5260  case 111: $function = 'CHAR'; $args = 1; break;
5261  case 112: $function = 'LOWER'; $args = 1; break;
5262  case 113: $function = 'UPPER'; $args = 1; break;
5263  case 114: $function = 'PROPER'; $args = 1; break;
5264  case 117: $function = 'EXACT'; $args = 2; break;
5265  case 118: $function = 'TRIM'; $args = 1; break;
5266  case 119: $function = 'REPLACE'; $args = 4; break;
5267  case 121: $function = 'CODE'; $args = 1; break;
5268  case 126: $function = 'ISERR'; $args = 1; break;
5269  case 127: $function = 'ISTEXT'; $args = 1; break;
5270  case 128: $function = 'ISNUMBER'; $args = 1; break;
5271  case 129: $function = 'ISBLANK'; $args = 1; break;
5272  case 130: $function = 'T'; $args = 1; break;
5273  case 131: $function = 'N'; $args = 1; break;
5274  case 140: $function = 'DATEVALUE'; $args = 1; break;
5275  case 141: $function = 'TIMEVALUE'; $args = 1; break;
5276  case 142: $function = 'SLN'; $args = 3; break;
5277  case 143: $function = 'SYD'; $args = 4; break;
5278  case 162: $function = 'CLEAN'; $args = 1; break;
5279  case 163: $function = 'MDETERM'; $args = 1; break;
5280  case 164: $function = 'MINVERSE'; $args = 1; break;
5281  case 165: $function = 'MMULT'; $args = 2; break;
5282  case 184: $function = 'FACT'; $args = 1; break;
5283  case 189: $function = 'DPRODUCT'; $args = 3; break;
5284  case 190: $function = 'ISNONTEXT'; $args = 1; break;
5285  case 195: $function = 'DSTDEVP'; $args = 3; break;
5286  case 196: $function = 'DVARP'; $args = 3; break;
5287  case 198: $function = 'ISLOGICAL'; $args = 1; break;
5288  case 199: $function = 'DCOUNTA'; $args = 3; break;
5289  case 207: $function = 'REPLACEB'; $args = 4; break;
5290  case 210: $function = 'MIDB'; $args = 3; break;
5291  case 211: $function = 'LENB'; $args = 1; break;
5292  case 212: $function = 'ROUNDUP'; $args = 2; break;
5293  case 213: $function = 'ROUNDDOWN'; $args = 2; break;
5294  case 214: $function = 'ASC'; $args = 1; break;
5295  case 215: $function = 'DBCS'; $args = 1; break;
5296  case 221: $function = 'TODAY'; $args = 0; break;
5297  case 229: $function = 'SINH'; $args = 1; break;
5298  case 230: $function = 'COSH'; $args = 1; break;
5299  case 231: $function = 'TANH'; $args = 1; break;
5300  case 232: $function = 'ASINH'; $args = 1; break;
5301  case 233: $function = 'ACOSH'; $args = 1; break;
5302  case 234: $function = 'ATANH'; $args = 1; break;
5303  case 235: $function = 'DGET'; $args = 3; break;
5304  case 244: $function = 'INFO'; $args = 1; break;
5305  case 252: $function = 'FREQUENCY'; $args = 2; break;
5306  case 261: $function = 'ERROR.TYPE'; $args = 1; break;
5307  case 271: $function = 'GAMMALN'; $args = 1; break;
5308  case 273: $function = 'BINOMDIST'; $args = 4; break;
5309  case 274: $function = 'CHIDIST'; $args = 2; break;
5310  case 275: $function = 'CHIINV'; $args = 2; break;
5311  case 276: $function = 'COMBIN'; $args = 2; break;
5312  case 277: $function = 'CONFIDENCE'; $args = 3; break;
5313  case 278: $function = 'CRITBINOM'; $args = 3; break;
5314  case 279: $function = 'EVEN'; $args = 1; break;
5315  case 280: $function = 'EXPONDIST'; $args = 3; break;
5316  case 281: $function = 'FDIST'; $args = 3; break;
5317  case 282: $function = 'FINV'; $args = 3; break;
5318  case 283: $function = 'FISHER'; $args = 1; break;
5319  case 284: $function = 'FISHERINV'; $args = 1; break;
5320  case 285: $function = 'FLOOR'; $args = 2; break;
5321  case 286: $function = 'GAMMADIST'; $args = 4; break;
5322  case 287: $function = 'GAMMAINV'; $args = 3; break;
5323  case 288: $function = 'CEILING'; $args = 2; break;
5324  case 289: $function = 'HYPGEOMDIST'; $args = 4; break;
5325  case 290: $function = 'LOGNORMDIST'; $args = 3; break;
5326  case 291: $function = 'LOGINV'; $args = 3; break;
5327  case 292: $function = 'NEGBINOMDIST'; $args = 3; break;
5328  case 293: $function = 'NORMDIST'; $args = 4; break;
5329  case 294: $function = 'NORMSDIST'; $args = 1; break;
5330  case 295: $function = 'NORMINV'; $args = 3; break;
5331  case 296: $function = 'NORMSINV'; $args = 1; break;
5332  case 297: $function = 'STANDARDIZE'; $args = 3; break;
5333  case 298: $function = 'ODD'; $args = 1; break;
5334  case 299: $function = 'PERMUT'; $args = 2; break;
5335  case 300: $function = 'POISSON'; $args = 3; break;
5336  case 301: $function = 'TDIST'; $args = 3; break;
5337  case 302: $function = 'WEIBULL'; $args = 4; break;
5338  case 303: $function = 'SUMXMY2'; $args = 2; break;
5339  case 304: $function = 'SUMX2MY2'; $args = 2; break;
5340  case 305: $function = 'SUMX2PY2'; $args = 2; break;
5341  case 306: $function = 'CHITEST'; $args = 2; break;
5342  case 307: $function = 'CORREL'; $args = 2; break;
5343  case 308: $function = 'COVAR'; $args = 2; break;
5344  case 309: $function = 'FORECAST'; $args = 3; break;
5345  case 310: $function = 'FTEST'; $args = 2; break;
5346  case 311: $function = 'INTERCEPT'; $args = 2; break;
5347  case 312: $function = 'PEARSON'; $args = 2; break;
5348  case 313: $function = 'RSQ'; $args = 2; break;
5349  case 314: $function = 'STEYX'; $args = 2; break;
5350  case 315: $function = 'SLOPE'; $args = 2; break;
5351  case 316: $function = 'TTEST'; $args = 4; break;
5352  case 325: $function = 'LARGE'; $args = 2; break;
5353  case 326: $function = 'SMALL'; $args = 2; break;
5354  case 327: $function = 'QUARTILE'; $args = 2; break;
5355  case 328: $function = 'PERCENTILE'; $args = 2; break;
5356  case 331: $function = 'TRIMMEAN'; $args = 2; break;
5357  case 332: $function = 'TINV'; $args = 2; break;
5358  case 337: $function = 'POWER'; $args = 2; break;
5359  case 342: $function = 'RADIANS'; $args = 1; break;
5360  case 343: $function = 'DEGREES'; $args = 1; break;
5361  case 346: $function = 'COUNTIF'; $args = 2; break;
5362  case 347: $function = 'COUNTBLANK'; $args = 1; break;
5363  case 350: $function = 'ISPMT'; $args = 4; break;
5364  case 351: $function = 'DATEDIF'; $args = 3; break;
5365  case 352: $function = 'DATESTRING'; $args = 1; break;
5366  case 353: $function = 'NUMBERSTRING'; $args = 2; break;
5367  case 360: $function = 'PHONETIC'; $args = 1; break;
5368  case 368: $function = 'BAHTTEXT'; $args = 1; break;
5369  default:
5370  throw new Exception('Unrecognized function in formula');
5371  break;
5372  }
5373  $data = array('function' => $function, 'args' => $args);
5374  break;
5375  case 0x22: // function with variable number of arguments
5376  case 0x42:
5377  case 0x62:
5378  $name = 'tFuncV';
5379  $size = 4;
5380  // offset: 1; size: 1; number of arguments
5381  $args = ord($formulaData[1]);
5382  // offset: 2: size: 2; index to built-in sheet function
5383  $index = self::_GetInt2d($formulaData, 2);
5384  switch ($index) {
5385  case 0: $function = 'COUNT'; break;
5386  case 1: $function = 'IF'; break;
5387  case 4: $function = 'SUM'; break;
5388  case 5: $function = 'AVERAGE'; break;
5389  case 6: $function = 'MIN'; break;
5390  case 7: $function = 'MAX'; break;
5391  case 8: $function = 'ROW'; break;
5392  case 9: $function = 'COLUMN'; break;
5393  case 11: $function = 'NPV'; break;
5394  case 12: $function = 'STDEV'; break;
5395  case 13: $function = 'DOLLAR'; break;
5396  case 14: $function = 'FIXED'; break;
5397  case 28: $function = 'LOOKUP'; break;
5398  case 29: $function = 'INDEX'; break;
5399  case 36: $function = 'AND'; break;
5400  case 37: $function = 'OR'; break;
5401  case 46: $function = 'VAR'; break;
5402  case 49: $function = 'LINEST'; break;
5403  case 50: $function = 'TREND'; break;
5404  case 51: $function = 'LOGEST'; break;
5405  case 52: $function = 'GROWTH'; break;
5406  case 56: $function = 'PV'; break;
5407  case 57: $function = 'FV'; break;
5408  case 58: $function = 'NPER'; break;
5409  case 59: $function = 'PMT'; break;
5410  case 60: $function = 'RATE'; break;
5411  case 62: $function = 'IRR'; break;
5412  case 64: $function = 'MATCH'; break;
5413  case 70: $function = 'WEEKDAY'; break;
5414  case 78: $function = 'OFFSET'; break;
5415  case 82: $function = 'SEARCH'; break;
5416  case 100: $function = 'CHOOSE'; break;
5417  case 101: $function = 'HLOOKUP'; break;
5418  case 102: $function = 'VLOOKUP'; break;
5419  case 109: $function = 'LOG'; break;
5420  case 115: $function = 'LEFT'; break;
5421  case 116: $function = 'RIGHT'; break;
5422  case 120: $function = 'SUBSTITUTE'; break;
5423  case 124: $function = 'FIND'; break;
5424  case 125: $function = 'CELL'; break;
5425  case 144: $function = 'DDB'; break;
5426  case 148: $function = 'INDIRECT'; break;
5427  case 167: $function = 'IPMT'; break;
5428  case 168: $function = 'PPMT'; break;
5429  case 169: $function = 'COUNTA'; break;
5430  case 183: $function = 'PRODUCT'; break;
5431  case 193: $function = 'STDEVP'; break;
5432  case 194: $function = 'VARP'; break;
5433  case 197: $function = 'TRUNC'; break;
5434  case 204: $function = 'USDOLLAR'; break;
5435  case 205: $function = 'FINDB'; break;
5436  case 206: $function = 'SEARCHB'; break;
5437  case 208: $function = 'LEFTB'; break;
5438  case 209: $function = 'RIGHTB'; break;
5439  case 216: $function = 'RANK'; break;
5440  case 219: $function = 'ADDRESS'; break;
5441  case 220: $function = 'DAYS360'; break;
5442  case 222: $function = 'VDB'; break;
5443  case 227: $function = 'MEDIAN'; break;
5444  case 228: $function = 'SUMPRODUCT'; break;
5445  case 247: $function = 'DB'; break;
5446  case 255: $function = ''; break;
5447  case 269: $function = 'AVEDEV'; break;
5448  case 270: $function = 'BETADIST'; break;
5449  case 272: $function = 'BETAINV'; break;
5450  case 317: $function = 'PROB'; break;
5451  case 318: $function = 'DEVSQ'; break;
5452  case 319: $function = 'GEOMEAN'; break;
5453  case 320: $function = 'HARMEAN'; break;
5454  case 321: $function = 'SUMSQ'; break;
5455  case 322: $function = 'KURT'; break;
5456  case 323: $function = 'SKEW'; break;
5457  case 324: $function = 'ZTEST'; break;
5458  case 329: $function = 'PERCENTRANK'; break;
5459  case 330: $function = 'MODE'; break;
5460  case 336: $function = 'CONCATENATE'; break;
5461  case 344: $function = 'SUBTOTAL'; break;
5462  case 345: $function = 'SUMIF'; break;
5463  case 354: $function = 'ROMAN'; break;
5464  case 358: $function = 'GETPIVOTDATA'; break;
5465  case 359: $function = 'HYPERLINK'; break;
5466  case 361: $function = 'AVERAGEA'; break;
5467  case 362: $function = 'MAXA'; break;
5468  case 363: $function = 'MINA'; break;
5469  case 364: $function = 'STDEVPA'; break;
5470  case 365: $function = 'VARPA'; break;
5471  case 366: $function = 'STDEVA'; break;
5472  case 367: $function = 'VARA'; break;
5473  default:
5474  throw new Exception('Unrecognized function in formula');
5475  break;
5476  }
5477  $data = array('function' => $function, 'args' => $args);
5478  break;
5479  case 0x23: // index to defined name
5480  case 0x43:
5481  case 0x63:
5482  $name = 'tName';
5483  $size = 5;
5484  // offset: 1; size: 2; one-based index to definedname record
5485  $definedNameIndex = self::_GetInt2d($formulaData, 1) - 1;
5486  // offset: 2; size: 2; not used
5487  $data = $this->_definedname[$definedNameIndex]['name'];
5488  break;
5489  case 0x24: // single cell reference e.g. A5
5490  case 0x44:
5491  case 0x64:
5492  $name = 'tRef';
5493  $size = 5;
5494  $data = $this->_readBIFF8CellAddress(substr($formulaData, 1, 4));
5495  break;
5496  case 0x25: // cell range reference to cells in the same sheet (2d)
5497  case 0x45:
5498  case 0x65:
5499  $name = 'tArea';
5500  $size = 9;
5501  $data = $this->_readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
5502  break;
5503  case 0x26: // Constant reference sub-expression
5504  case 0x46:
5505  case 0x66:
5506  $name = 'tMemArea';
5507  // offset: 1; size: 4; not used
5508  // offset: 5; size: 2; size of the following subexpression
5509  $subSize = self::_GetInt2d($formulaData, 5);
5510  $size = 7 + $subSize;
5511  $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
5512  break;
5513  case 0x27: // Deleted constant reference sub-expression
5514  case 0x47:
5515  case 0x67:
5516  $name = 'tMemErr';
5517  // offset: 1; size: 4; not used
5518  // offset: 5; size: 2; size of the following subexpression
5519  $subSize = self::_GetInt2d($formulaData, 5);
5520  $size = 7 + $subSize;
5521  $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
5522  break;
5523  case 0x29: // Variable reference sub-expression
5524  case 0x49:
5525  case 0x69:
5526  $name = 'tMemFunc';
5527  // offset: 1; size: 2; size of the following sub-expression
5528  $subSize = self::_GetInt2d($formulaData, 1);
5529  $size = 3 + $subSize;
5530  $data = $this->_getFormulaFromData(substr($formulaData, 3, $subSize));
5531  break;
5532 
5533  case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
5534  case 0x4C:
5535  case 0x6C:
5536  $name = 'tRefN';
5537  $size = 5;
5538  $data = $this->_readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
5539  break;
5540 
5541  case 0x2D: // Relative 2d range reference
5542  case 0x4D:
5543  case 0x6D:
5544  $name = 'tAreaN';
5545  $size = 9;
5546  $data = $this->_readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
5547  break;
5548 
5549  case 0x39: // External name
5550  case 0x59:
5551  case 0x79:
5552  $name = 'tNameX';
5553  $size = 7;
5554  // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
5555  // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
5556  $index = self::_GetInt2d($formulaData, 3);
5557  // assume index is to EXTERNNAME record
5558  $data = $this->_externalNames[$index - 1]['name'];
5559  // offset: 5; size: 2; not used
5560  break;
5561 
5562  case 0x3A: // 3d reference to cell
5563  case 0x5A:
5564  case 0x7A:
5565  $name = 'tRef3d';
5566  $size = 7;
5567 
5568  try {
5569  // offset: 1; size: 2; index to REF entry
5570  $sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1));
5571  // offset: 3; size: 4; cell address
5572  $cellAddress = $this->_readBIFF8CellAddress(substr($formulaData, 3, 4));
5573 
5574  $data = "$sheetRange!$cellAddress";
5575  } catch (Exception $e) {
5576  // deleted sheet reference
5577  $data = '#REF!';
5578  }
5579 
5580  break;
5581  case 0x3B: // 3d reference to cell range
5582  case 0x5B:
5583  case 0x7B:
5584  $name = 'tArea3d';
5585  $size = 11;
5586 
5587  try {
5588  // offset: 1; size: 2; index to REF entry
5589  $sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1));
5590  // offset: 3; size: 8; cell address
5591  $cellRangeAddress = $this->_readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
5592 
5593  $data = "$sheetRange!$cellRangeAddress";
5594  } catch (Exception $e) {
5595  // deleted sheet reference
5596  $data = '#REF!';
5597  }
5598 
5599  break;
5600  // Unknown cases // don't know how to deal with
5601  default:
5602  throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
5603  break;
5604  }
5605 
5606  return array(
5607  'id' => $id,
5608  'name' => $name,
5609  'size' => $size,
5610  'data' => $data,
5611  );
5612  }
5613 
5621  private function _readBIFF8CellAddress($cellAddressStructure)
5622  {
5623  // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
5624  $row = self::_GetInt2d($cellAddressStructure, 0) + 1;
5625 
5626  // offset: 2; size: 2; index to column or column offset + relative flags
5627 
5628  // bit: 7-0; mask 0x00FF; column index
5629  $column = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($cellAddressStructure, 2));
5630 
5631  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5632  if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) {
5633  $column = '$' . $column;
5634  }
5635  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5636  if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) {
5637  $row = '$' . $row;
5638  }
5639 
5640  return $column . $row;
5641  }
5642 
5652  private function _readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
5653  {
5654  list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
5655  $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
5656 
5657  // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
5658  $rowIndex = self::_GetInt2d($cellAddressStructure, 0);
5659  $row = self::_GetInt2d($cellAddressStructure, 0) + 1;
5660 
5661  // offset: 2; size: 2; index to column or column offset + relative flags
5662 
5663  // bit: 7-0; mask 0x00FF; column index
5664  $colIndex = 0x00FF & self::_GetInt2d($cellAddressStructure, 2);
5665 
5666  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5667  if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) {
5668  $column = PHPExcel_Cell::stringFromColumnIndex($colIndex);
5669  $column = '$' . $column;
5670  } else {
5671  $colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256;
5672  $column = PHPExcel_Cell::stringFromColumnIndex($baseCol + $colIndex);
5673  }
5674 
5675  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5676  if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) {
5677  $row = '$' . $row;
5678  } else {
5679  $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
5680  $row = $baseRow + $rowIndex;
5681  }
5682 
5683  return $column . $row;
5684  }
5685 
5695  private function _readBIFF5CellRangeAddressFixed($subData)
5696  {
5697  // offset: 0; size: 2; index to first row
5698  $fr = self::_GetInt2d($subData, 0) + 1;
5699 
5700  // offset: 2; size: 2; index to last row
5701  $lr = self::_GetInt2d($subData, 2) + 1;
5702 
5703  // offset: 4; size: 1; index to first column
5704  $fc = ord($subData{4});
5705 
5706  // offset: 5; size: 1; index to last column
5707  $lc = ord($subData{5});
5708 
5709  // check values
5710  if ($fr > $lr || $fc > $lc) {
5711  throw new Exception('Not a cell range address');
5712  }
5713 
5714  // column index to letter
5717 
5718  if ($fr == $lr and $fc == $lc) {
5719  return "$fc$fr";
5720  }
5721  return "$fc$fr:$lc$lr";
5722  }
5723 
5733  private function _readBIFF8CellRangeAddressFixed($subData)
5734  {
5735  // offset: 0; size: 2; index to first row
5736  $fr = self::_GetInt2d($subData, 0) + 1;
5737 
5738  // offset: 2; size: 2; index to last row
5739  $lr = self::_GetInt2d($subData, 2) + 1;
5740 
5741  // offset: 4; size: 2; index to first column
5742  $fc = self::_GetInt2d($subData, 4);
5743 
5744  // offset: 6; size: 2; index to last column
5745  $lc = self::_GetInt2d($subData, 6);
5746 
5747  // check values
5748  if ($fr > $lr || $fc > $lc) {
5749  throw new Exception('Not a cell range address');
5750  }
5751 
5752  // column index to letter
5755 
5756  if ($fr == $lr and $fc == $lc) {
5757  return "$fc$fr";
5758  }
5759  return "$fc$fr:$lc$lr";
5760  }
5761 
5770  private function _readBIFF8CellRangeAddress($subData)
5771  {
5772  // todo: if cell range is just a single cell, should this funciton
5773  // not just return e.g. 'A1' and not 'A1:A1' ?
5774 
5775  // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
5776  $fr = self::_GetInt2d($subData, 0) + 1;
5777 
5778  // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
5779  $lr = self::_GetInt2d($subData, 2) + 1;
5780 
5781  // offset: 4; size: 2; index to first column or column offset + relative flags
5782 
5783  // bit: 7-0; mask 0x00FF; column index
5784  $fc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 4));
5785 
5786  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5787  if (!(0x4000 & self::_GetInt2d($subData, 4))) {
5788  $fc = '$' . $fc;
5789  }
5790 
5791  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5792  if (!(0x8000 & self::_GetInt2d($subData, 4))) {
5793  $fr = '$' . $fr;
5794  }
5795 
5796  // offset: 6; size: 2; index to last column or column offset + relative flags
5797 
5798  // bit: 7-0; mask 0x00FF; column index
5799  $lc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 6));
5800 
5801  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5802  if (!(0x4000 & self::_GetInt2d($subData, 6))) {
5803  $lc = '$' . $lc;
5804  }
5805 
5806  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5807  if (!(0x8000 & self::_GetInt2d($subData, 6))) {
5808  $lr = '$' . $lr;
5809  }
5810 
5811  return "$fc$fr:$lc$lr";
5812  }
5813 
5823  private function _readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
5824  {
5825  list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
5826  $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
5827 
5828  // TODO: if cell range is just a single cell, should this funciton
5829  // not just return e.g. 'A1' and not 'A1:A1' ?
5830 
5831  // offset: 0; size: 2; first row
5832  $frIndex = self::_GetInt2d($subData, 0); // adjust below
5833 
5834  // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
5835  $lrIndex = self::_GetInt2d($subData, 2); // adjust below
5836 
5837  // offset: 4; size: 2; first column with relative/absolute flags
5838 
5839  // bit: 7-0; mask 0x00FF; column index
5840  $fcIndex = 0x00FF & self::_GetInt2d($subData, 4);
5841 
5842  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5843  if (!(0x4000 & self::_GetInt2d($subData, 4))) {
5844  // absolute column index
5845  $fc = PHPExcel_Cell::stringFromColumnIndex($fcIndex);
5846  $fc = '$' . $fc;
5847  } else {
5848  // column offset
5849  $fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256;
5850  $fc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $fcIndex);
5851  }
5852 
5853  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5854  if (!(0x8000 & self::_GetInt2d($subData, 4))) {
5855  // absolute row index
5856  $fr = $frIndex + 1;
5857  $fr = '$' . $fr;
5858  } else {
5859  // row offset
5860  $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
5861  $fr = $baseRow + $frIndex;
5862  }
5863 
5864  // offset: 6; size: 2; last column with relative/absolute flags
5865 
5866  // bit: 7-0; mask 0x00FF; column index
5867  $lcIndex = 0x00FF & self::_GetInt2d($subData, 6);
5868  $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
5869  $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
5870 
5871  // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
5872  if (!(0x4000 & self::_GetInt2d($subData, 6))) {
5873  // absolute column index
5874  $lc = PHPExcel_Cell::stringFromColumnIndex($lcIndex);
5875  $lc = '$' . $lc;
5876  } else {
5877  // column offset
5878  $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
5879  $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
5880  }
5881 
5882  // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
5883  if (!(0x8000 & self::_GetInt2d($subData, 6))) {
5884  // absolute row index
5885  $lr = $lrIndex + 1;
5886  $lr = '$' . $lr;
5887  } else {
5888  // row offset
5889  $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
5890  $lr = $baseRow + $lrIndex;
5891  }
5892 
5893  return "$fc$fr:$lc$lr";
5894  }
5895 
5903  private function _readBIFF8CellRangeAddressList($subData)
5904  {
5905  $cellRangeAddresses = array();
5906 
5907  // offset: 0; size: 2; number of the following cell range addresses
5908  $nm = self::_GetInt2d($subData, 0);
5909 
5910  $offset = 2;
5911  // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
5912  for ($i = 0; $i < $nm; ++$i) {
5913  $cellRangeAddresses[] = $this->_readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
5914  $offset += 8;
5915  }
5916 
5917  return array(
5918  'size' => 2 + 8 * $nm,
5919  'cellRangeAddresses' => $cellRangeAddresses,
5920  );
5921  }
5922 
5930  private function _readBIFF5CellRangeAddressList($subData)
5931  {
5932  $cellRangeAddresses = array();
5933 
5934  // offset: 0; size: 2; number of the following cell range addresses
5935  $nm = self::_GetInt2d($subData, 0);
5936 
5937  $offset = 2;
5938  // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
5939  for ($i = 0; $i < $nm; ++$i) {
5940  $cellRangeAddresses[] = $this->_readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
5941  $offset += 6;
5942  }
5943 
5944  return array(
5945  'size' => 2 + 6 * $nm,
5946  'cellRangeAddresses' => $cellRangeAddresses,
5947  );
5948  }
5949 
5960  private function _readSheetRangeByRefIndex($index)
5961  {
5962  if (isset($this->_ref[$index])) {
5963 
5964  $type = $this->_externalBooks[$this->_ref[$index]['externalBookIndex']]['type'];
5965 
5966  switch ($type) {
5967  case 'internal':
5968  // check if we have a deleted 3d reference
5969  if ($this->_ref[$index]['firstSheetIndex'] == 0xFFFF or $this->_ref[$index]['lastSheetIndex'] == 0xFFFF) {
5970  throw new Exception('Deleted sheet reference');
5971  }
5972 
5973  // we have normal sheet range (collapsed or uncollapsed)
5974  $firstSheetName = $this->_sheets[$this->_ref[$index]['firstSheetIndex']]['name'];
5975  $lastSheetName = $this->_sheets[$this->_ref[$index]['lastSheetIndex']]['name'];
5976 
5977  if ($firstSheetName == $lastSheetName) {
5978  // collapsed sheet range
5979  $sheetRange = $firstSheetName;
5980  } else {
5981  $sheetRange = "$firstSheetName:$lastSheetName";
5982  }
5983 
5984  // escape the single-quotes
5985  $sheetRange = str_replace("'", "''", $sheetRange);
5986 
5987  // if there are special characters, we need to enclose the range in single-quotes
5988  // todo: check if we have identified the whole set of special characters
5989  // it seems that the following characters are not accepted for sheet names
5990  // and we may assume that they are not present: []*/:\?
5991  if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
5992  $sheetRange = "'$sheetRange'";
5993  }
5994 
5995  return $sheetRange;
5996  break;
5997 
5998  default:
5999  // TODO: external sheet support
6000  throw new Exception('Excel5 reader only supports internal sheets in fomulas');
6001  break;
6002  }
6003  }
6004  return false;
6005  }
6006 
6015  private static function _readBIFF8ConstantArray($arrayData)
6016  {
6017  // offset: 0; size: 1; number of columns decreased by 1
6018  $nc = ord($arrayData[0]);
6019 
6020  // offset: 1; size: 2; number of rows decreased by 1
6021  $nr = self::_GetInt2d($arrayData, 1);
6022  $size = 3; // initialize
6023  $arrayData = substr($arrayData, 3);
6024 
6025  // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
6026  $matrixChunks = array();
6027  for ($r = 1; $r <= $nr + 1; ++$r) {
6028  $items = array();
6029  for ($c = 1; $c <= $nc + 1; ++$c) {
6030  $constant = self::_readBIFF8Constant($arrayData);
6031  $items[] = $constant['value'];
6032  $arrayData = substr($arrayData, $constant['size']);
6033  $size += $constant['size'];
6034  }
6035  $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
6036  }
6037  $matrix = '{' . implode(';', $matrixChunks) . '}';
6038 
6039  return array(
6040  'value' => $matrix,
6041  'size' => $size,
6042  );
6043  }
6044 
6053  private static function _readBIFF8Constant($valueData)
6054  {
6055  // offset: 0; size: 1; identifier for type of constant
6056  $identifier = ord($valueData[0]);
6057 
6058  switch ($identifier) {
6059  case 0x00: // empty constant (what is this?)
6060  $value = '';
6061  $size = 9;
6062  break;
6063  case 0x01: // number
6064  // offset: 1; size: 8; IEEE 754 floating-point value
6065  $value = self::_extractNumber(substr($valueData, 1, 8));
6066  $size = 9;
6067  break;
6068  case 0x02: // string value
6069  // offset: 1; size: var; Unicode string, 16-bit string length
6070  $string = self::_readUnicodeStringLong(substr($valueData, 1));
6071  $value = '"' . $string['value'] . '"';
6072  $size = 1 + $string['size'];
6073  break;
6074  case 0x04: // boolean
6075  // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
6076  if (ord($valueData[1])) {
6077  $value = 'TRUE';
6078  } else {
6079  $value = 'FALSE';
6080  }
6081  $size = 9;
6082  break;
6083  case 0x10: // error code
6084  // offset: 1; size: 1; error code
6085  $value = self::_mapErrorCode(ord($valueData[1]));
6086  $size = 9;
6087  break;
6088  }
6089  return array(
6090  'value' => $value,
6091  'size' => $size,
6092  );
6093  }
6094 
6102  private static function _readRGB($rgb)
6103  {
6104  // offset: 0; size 1; Red component
6105  $r = ord($rgb{0});
6106 
6107  // offset: 1; size: 1; Green component
6108  $g = ord($rgb{1});
6109 
6110  // offset: 2; size: 1; Blue component
6111  $b = ord($rgb{2});
6112 
6113  // HEX notation, e.g. 'FF00FC'
6114  $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
6115 
6116  return array('rgb' => $rgb);
6117  }
6118 
6126  private function _readByteStringShort($subData)
6127  {
6128  // offset: 0; size: 1; length of the string (character count)
6129  $ln = ord($subData[0]);
6130 
6131  // offset: 1: size: var; character array (8-bit characters)
6132  $value = $this->_decodeCodepage(substr($subData, 1, $ln));
6133 
6134  return array(
6135  'value' => $value,
6136  'size' => 1 + $ln, // size in bytes of data structure
6137  );
6138  }
6139 
6147  private function _readByteStringLong($subData)
6148  {
6149  // offset: 0; size: 2; length of the string (character count)
6150  $ln = self::_GetInt2d($subData, 0);
6151 
6152  // offset: 2: size: var; character array (8-bit characters)
6153  $value = $this->_decodeCodepage(substr($subData, 2));
6154 
6155  //return $string;
6156  return array(
6157  'value' => $value,
6158  'size' => 2 + $ln, // size in bytes of data structure
6159  );
6160  }
6161 
6170  private static function _readUnicodeStringShort($subData)
6171  {
6172  $value = '';
6173 
6174  // offset: 0: size: 1; length of the string (character count)
6175  $characterCount = ord($subData[0]);
6176 
6177  $string = self::_readUnicodeString(substr($subData, 1), $characterCount);
6178 
6179  // add 1 for the string length
6180  $string['size'] += 1;
6181 
6182  return $string;
6183  }
6184 
6193  private static function _readUnicodeStringLong($subData)
6194  {
6195  $value = '';
6196 
6197  // offset: 0: size: 2; length of the string (character count)
6198  $characterCount = self::_GetInt2d($subData, 0);
6199 
6200  $string = self::_readUnicodeString(substr($subData, 2), $characterCount);
6201 
6202  // add 2 for the string length
6203  $string['size'] += 2;
6204 
6205  return $string;
6206  }
6207 
6217  private static function _readUnicodeString($subData, $characterCount)
6218  {
6219  $value = '';
6220 
6221  // offset: 0: size: 1; option flags
6222 
6223  // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
6224  $isCompressed = !((0x01 & ord($subData[0])) >> 0);
6225 
6226  // bit: 2; mask: 0x04; Asian phonetic settings
6227  $hasAsian = (0x04) & ord($subData[0]) >> 2;
6228 
6229  // bit: 3; mask: 0x08; Rich-Text settings
6230  $hasRichText = (0x08) & ord($subData[0]) >> 3;
6231 
6232  // offset: 1: size: var; character array
6233  // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
6234  // needs to be fixed
6235  $value = self::_encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
6236 
6237  return array(
6238  'value' => $value,
6239  'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
6240  );
6241  }
6242 
6250  private static function _UTF8toExcelDoubleQuoted($value)
6251  {
6252  return '"' . str_replace('"', '""', $value) . '"';
6253  }
6254 
6261  private static function _extractNumber($data)
6262  {
6263  $rknumhigh = self::_GetInt4d($data, 4);
6264  $rknumlow = self::_GetInt4d($data, 0);
6265  $sign = ($rknumhigh & 0x80000000) >> 31;
6266  $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
6267  $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
6268  $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
6269  $mantissalow2 = ($rknumlow & 0x7fffffff);
6270  $value = $mantissa / pow( 2 , (20 - $exp));
6271 
6272  if ($mantissalow1 != 0) {
6273  $value += 1 / pow (2 , (21 - $exp));
6274  }
6275 
6276  $value += $mantissalow2 / pow (2 , (52 - $exp));
6277  if ($sign) {
6278  $value = -1 * $value;
6279  }
6280 
6281  return $value;
6282  }
6283 
6284  private static function _GetIEEE754($rknum)
6285  {
6286  if (($rknum & 0x02) != 0) {
6287  $value = $rknum >> 2;
6288  }
6289  else {
6290  // changes by mmp, info on IEEE754 encoding from
6291  // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
6292  // The RK format calls for using only the most significant 30 bits
6293  // of the 64 bit floating point value. The other 34 bits are assumed
6294  // to be 0 so we use the upper 30 bits of $rknum as follows...
6295  $sign = ($rknum & 0x80000000) >> 31;
6296  $exp = ($rknum & 0x7ff00000) >> 20;
6297  $mantissa = (0x100000 | ($rknum & 0x000ffffc));
6298  $value = $mantissa / pow( 2 , (20- ($exp - 1023)));
6299  if ($sign) {
6300  $value = -1 * $value;
6301  }
6302  //end of changes by mmp
6303  }
6304  if (($rknum & 0x01) != 0) {
6305  $value /= 100;
6306  }
6307  return $value;
6308  }
6309 
6317  private static function _encodeUTF16($string, $compressed = '')
6318  {
6319  if ($compressed) {
6320  $string = self::_uncompressByteString($string);
6321  }
6322 
6323  return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', 'UTF-16LE');
6324  }
6325 
6332  private static function _uncompressByteString($string)
6333  {
6334  $uncompressedString = '';
6335  $strLen = strlen($string);
6336  for ($i = 0; $i < $strLen; ++$i) {
6337  $uncompressedString .= $string[$i] . "\0";
6338  }
6339 
6340  return $uncompressedString;
6341  }
6342 
6349  private function _decodeCodepage($string)
6350  {
6351  return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', $this->_codepage);
6352  }
6353 
6361  public static function _GetInt2d($data, $pos)
6362  {
6363  return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
6364  }
6365 
6373  public static function _GetInt4d($data, $pos)
6374  {
6375  // FIX: represent numbers correctly on 64-bit system
6376  // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
6377  // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
6378  $_or_24 = ord($data[$pos + 3]);
6379  if ($_or_24 >= 128) {
6380  // negative number
6381  $_ord_24 = -abs((256 - $_or_24) << 24);
6382  } else {
6383  $_ord_24 = ($_or_24 & 127) << 24;
6384  }
6385  return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
6386  }
6387 
6395  private static function _readColor($color,$palette,$version)
6396  {
6397  if ($color <= 0x07 || $color >= 0x40) {
6398  // special built-in color
6399  return self::_mapBuiltInColor($color);
6400  } elseif (isset($palette) && isset($palette[$color - 8])) {
6401  // palette color, color index 0x08 maps to pallete index 0
6402  return $palette[$color - 8];
6403  } else {
6404  // default color table
6405  if ($version == self::XLS_BIFF8) {
6406  return self::_mapColor($color);
6407  } else {
6408  // BIFF5
6409  return self::_mapColorBIFF5($color);
6410  }
6411  }
6412 
6413  return $color;
6414  }
6415 
6416 
6424  private static function _mapBorderStyle($index)
6425  {
6426  switch ($index) {
6427  case 0x00: return PHPExcel_Style_Border::BORDER_NONE;
6428  case 0x01: return PHPExcel_Style_Border::BORDER_THIN;
6429  case 0x02: return PHPExcel_Style_Border::BORDER_MEDIUM;
6430  case 0x03: return PHPExcel_Style_Border::BORDER_DASHED;
6431  case 0x04: return PHPExcel_Style_Border::BORDER_DOTTED;
6432  case 0x05: return PHPExcel_Style_Border::BORDER_THICK;
6433  case 0x06: return PHPExcel_Style_Border::BORDER_DOUBLE;
6434  case 0x07: return PHPExcel_Style_Border::BORDER_HAIR;
6436  case 0x09: return PHPExcel_Style_Border::BORDER_DASHDOT;
6438  case 0x0B: return PHPExcel_Style_Border::BORDER_DASHDOTDOT;
6441  default: return PHPExcel_Style_Border::BORDER_NONE;
6442  }
6443  }
6444 
6452  private static function _mapFillPattern($index)
6453  {
6454  switch ($index) {
6455  case 0x00: return PHPExcel_Style_Fill::FILL_NONE;
6456  case 0x01: return PHPExcel_Style_Fill::FILL_SOLID;
6463  case 0x08: return PHPExcel_Style_Fill::FILL_PATTERN_DARKUP;
6474  default: return PHPExcel_Style_Fill::FILL_NONE;
6475  }
6476  }
6477 
6484  private static function _mapErrorCode($subData)
6485  {
6486  switch ($subData) {
6487  case 0x00: return '#NULL!'; break;
6488  case 0x07: return '#DIV/0!'; break;
6489  case 0x0F: return '#VALUE!'; break;
6490  case 0x17: return '#REF!'; break;
6491  case 0x1D: return '#NAME?'; break;
6492  case 0x24: return '#NUM!'; break;
6493  case 0x2A: return '#N/A'; break;
6494  default: return false;
6495  }
6496  }
6497 
6504  private static function _mapBuiltInColor($color)
6505  {
6506  switch ($color) {
6507  case 0x00: return array('rgb' => '000000');
6508  case 0x01: return array('rgb' => 'FFFFFF');
6509  case 0x02: return array('rgb' => 'FF0000');
6510  case 0x03: return array('rgb' => '00FF00');
6511  case 0x04: return array('rgb' => '0000FF');
6512  case 0x05: return array('rgb' => 'FFFF00');
6513  case 0x06: return array('rgb' => 'FF00FF');
6514  case 0x07: return array('rgb' => '00FFFF');
6515  case 0x40: return array('rgb' => '000000'); // system window text color
6516  case 0x41: return array('rgb' => 'FFFFFF'); // system window background color
6517  default: return array('rgb' => '000000');
6518  }
6519  }
6520 
6527  private static function _mapColorBIFF5($subData)
6528  {
6529  switch ($subData) {
6530  case 0x08: return array('rgb' => '000000');
6531  case 0x09: return array('rgb' => 'FFFFFF');
6532  case 0x0A: return array('rgb' => 'FF0000');
6533  case 0x0B: return array('rgb' => '00FF00');
6534  case 0x0C: return array('rgb' => '0000FF');
6535  case 0x0D: return array('rgb' => 'FFFF00');
6536  case 0x0E: return array('rgb' => 'FF00FF');
6537  case 0x0F: return array('rgb' => '00FFFF');
6538  case 0x10: return array('rgb' => '800000');
6539  case 0x11: return array('rgb' => '008000');
6540  case 0x12: return array('rgb' => '000080');
6541  case 0x13: return array('rgb' => '808000');
6542  case 0x14: return array('rgb' => '800080');
6543  case 0x15: return array('rgb' => '008080');
6544  case 0x16: return array('rgb' => 'C0C0C0');
6545  case 0x17: return array('rgb' => '808080');
6546  case 0x18: return array('rgb' => '8080FF');
6547  case 0x19: return array('rgb' => '802060');
6548  case 0x1A: return array('rgb' => 'FFFFC0');
6549  case 0x1B: return array('rgb' => 'A0E0F0');
6550  case 0x1C: return array('rgb' => '600080');
6551  case 0x1D: return array('rgb' => 'FF8080');
6552  case 0x1E: return array('rgb' => '0080C0');
6553  case 0x1F: return array('rgb' => 'C0C0FF');
6554  case 0x20: return array('rgb' => '000080');
6555  case 0x21: return array('rgb' => 'FF00FF');
6556  case 0x22: return array('rgb' => 'FFFF00');
6557  case 0x23: return array('rgb' => '00FFFF');
6558  case 0x24: return array('rgb' => '800080');
6559  case 0x25: return array('rgb' => '800000');
6560  case 0x26: return array('rgb' => '008080');
6561  case 0x27: return array('rgb' => '0000FF');
6562  case 0x28: return array('rgb' => '00CFFF');
6563  case 0x29: return array('rgb' => '69FFFF');
6564  case 0x2A: return array('rgb' => 'E0FFE0');
6565  case 0x2B: return array('rgb' => 'FFFF80');
6566  case 0x2C: return array('rgb' => 'A6CAF0');
6567  case 0x2D: return array('rgb' => 'DD9CB3');
6568  case 0x2E: return array('rgb' => 'B38FEE');
6569  case 0x2F: return array('rgb' => 'E3E3E3');
6570  case 0x30: return array('rgb' => '2A6FF9');
6571  case 0x31: return array('rgb' => '3FB8CD');
6572  case 0x32: return array('rgb' => '488436');
6573  case 0x33: return array('rgb' => '958C41');
6574  case 0x34: return array('rgb' => '8E5E42');
6575  case 0x35: return array('rgb' => 'A0627A');
6576  case 0x36: return array('rgb' => '624FAC');
6577  case 0x37: return array('rgb' => '969696');
6578  case 0x38: return array('rgb' => '1D2FBE');
6579  case 0x39: return array('rgb' => '286676');
6580  case 0x3A: return array('rgb' => '004500');
6581  case 0x3B: return array('rgb' => '453E01');
6582  case 0x3C: return array('rgb' => '6A2813');
6583  case 0x3D: return array('rgb' => '85396A');
6584  case 0x3E: return array('rgb' => '4A3285');
6585  case 0x3F: return array('rgb' => '424242');
6586  default: return array('rgb' => '000000');
6587  }
6588  }
6589 
6596  private static function _mapColor($subData)
6597  {
6598  switch ($subData) {
6599  case 0x08: return array('rgb' => '000000');
6600  case 0x09: return array('rgb' => 'FFFFFF');
6601  case 0x0A: return array('rgb' => 'FF0000');
6602  case 0x0B: return array('rgb' => '00FF00');
6603  case 0x0C: return array('rgb' => '0000FF');
6604  case 0x0D: return array('rgb' => 'FFFF00');
6605  case 0x0E: return array('rgb' => 'FF00FF');
6606  case 0x0F: return array('rgb' => '00FFFF');
6607  case 0x10: return array('rgb' => '800000');
6608  case 0x11: return array('rgb' => '008000');
6609  case 0x12: return array('rgb' => '000080');
6610  case 0x13: return array('rgb' => '808000');
6611  case 0x14: return array('rgb' => '800080');
6612  case 0x15: return array('rgb' => '008080');
6613  case 0x16: return array('rgb' => 'C0C0C0');
6614  case 0x17: return array('rgb' => '808080');
6615  case 0x18: return array('rgb' => '9999FF');
6616  case 0x19: return array('rgb' => '993366');
6617  case 0x1A: return array('rgb' => 'FFFFCC');
6618  case 0x1B: return array('rgb' => 'CCFFFF');
6619  case 0x1C: return array('rgb' => '660066');
6620  case 0x1D: return array('rgb' => 'FF8080');
6621  case 0x1E: return array('rgb' => '0066CC');
6622  case 0x1F: return array('rgb' => 'CCCCFF');
6623  case 0x20: return array('rgb' => '000080');
6624  case 0x21: return array('rgb' => 'FF00FF');
6625  case 0x22: return array('rgb' => 'FFFF00');
6626  case 0x23: return array('rgb' => '00FFFF');
6627  case 0x24: return array('rgb' => '800080');
6628  case 0x25: return array('rgb' => '800000');
6629  case 0x26: return array('rgb' => '008080');
6630  case 0x27: return array('rgb' => '0000FF');
6631  case 0x28: return array('rgb' => '00CCFF');
6632  case 0x29: return array('rgb' => 'CCFFFF');
6633  case 0x2A: return array('rgb' => 'CCFFCC');
6634  case 0x2B: return array('rgb' => 'FFFF99');
6635  case 0x2C: return array('rgb' => '99CCFF');
6636  case 0x2D: return array('rgb' => 'FF99CC');
6637  case 0x2E: return array('rgb' => 'CC99FF');
6638  case 0x2F: return array('rgb' => 'FFCC99');
6639  case 0x30: return array('rgb' => '3366FF');
6640  case 0x31: return array('rgb' => '33CCCC');
6641  case 0x32: return array('rgb' => '99CC00');
6642  case 0x33: return array('rgb' => 'FFCC00');
6643  case 0x34: return array('rgb' => 'FF9900');
6644  case 0x35: return array('rgb' => 'FF6600');
6645  case 0x36: return array('rgb' => '666699');
6646  case 0x37: return array('rgb' => '969696');
6647  case 0x38: return array('rgb' => '003366');
6648  case 0x39: return array('rgb' => '339966');
6649  case 0x3A: return array('rgb' => '003300');
6650  case 0x3B: return array('rgb' => '333300');
6651  case 0x3C: return array('rgb' => '993300');
6652  case 0x3D: return array('rgb' => '993366');
6653  case 0x3E: return array('rgb' => '333399');
6654  case 0x3F: return array('rgb' => '333333');
6655  default: return array('rgb' => '000000');
6656  }
6657  }
6658 
6659  private function _parseRichText($is = '') {
6660  $value = new PHPExcel_RichText();
6661 
6662  $value->createText($is);
6663 
6664  return $value;
6665  }
6666 
6667 }