Gnumeric.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Reader;
  3. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  4. use PhpOffice\PhpSpreadsheet\Cell\DataType;
  5. use PhpOffice\PhpSpreadsheet\NamedRange;
  6. use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
  7. use PhpOffice\PhpSpreadsheet\ReferenceHelper;
  8. use PhpOffice\PhpSpreadsheet\RichText\RichText;
  9. use PhpOffice\PhpSpreadsheet\Settings;
  10. use PhpOffice\PhpSpreadsheet\Shared\Date;
  11. use PhpOffice\PhpSpreadsheet\Shared\File;
  12. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  13. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  14. use PhpOffice\PhpSpreadsheet\Style\Border;
  15. use PhpOffice\PhpSpreadsheet\Style\Borders;
  16. use PhpOffice\PhpSpreadsheet\Style\Fill;
  17. use PhpOffice\PhpSpreadsheet\Style\Font;
  18. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  19. use XMLReader;
  20. class Gnumeric extends BaseReader
  21. {
  22. /**
  23. * Shared Expressions.
  24. *
  25. * @var array
  26. */
  27. private $expressions = [];
  28. private $referenceHelper;
  29. /**
  30. * Create a new Gnumeric.
  31. */
  32. public function __construct()
  33. {
  34. $this->readFilter = new DefaultReadFilter();
  35. $this->referenceHelper = ReferenceHelper::getInstance();
  36. $this->securityScanner = XmlScanner::getInstance($this);
  37. }
  38. /**
  39. * Can the current IReader read the file?
  40. *
  41. * @param string $pFilename
  42. *
  43. * @throws Exception
  44. *
  45. * @return bool
  46. */
  47. public function canRead($pFilename)
  48. {
  49. File::assertFile($pFilename);
  50. // Check if gzlib functions are available
  51. if (!function_exists('gzread')) {
  52. throw new Exception('gzlib library is not enabled');
  53. }
  54. // Read signature data (first 3 bytes)
  55. $fh = fopen($pFilename, 'r');
  56. $data = fread($fh, 2);
  57. fclose($fh);
  58. return $data == chr(0x1F) . chr(0x8B);
  59. }
  60. /**
  61. * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
  62. *
  63. * @param string $pFilename
  64. *
  65. * @return array
  66. */
  67. public function listWorksheetNames($pFilename)
  68. {
  69. File::assertFile($pFilename);
  70. $xml = new XMLReader();
  71. $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
  72. $xml->setParserProperty(2, true);
  73. $worksheetNames = [];
  74. while ($xml->read()) {
  75. if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) {
  76. $xml->read(); // Move onto the value node
  77. $worksheetNames[] = (string) $xml->value;
  78. } elseif ($xml->name == 'gnm:Sheets') {
  79. // break out of the loop once we've got our sheet names rather than parse the entire file
  80. break;
  81. }
  82. }
  83. return $worksheetNames;
  84. }
  85. /**
  86. * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
  87. *
  88. * @param string $pFilename
  89. *
  90. * @return array
  91. */
  92. public function listWorksheetInfo($pFilename)
  93. {
  94. File::assertFile($pFilename);
  95. $xml = new XMLReader();
  96. $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
  97. $xml->setParserProperty(2, true);
  98. $worksheetInfo = [];
  99. while ($xml->read()) {
  100. if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) {
  101. $tmpInfo = [
  102. 'worksheetName' => '',
  103. 'lastColumnLetter' => 'A',
  104. 'lastColumnIndex' => 0,
  105. 'totalRows' => 0,
  106. 'totalColumns' => 0,
  107. ];
  108. while ($xml->read()) {
  109. if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) {
  110. $xml->read(); // Move onto the value node
  111. $tmpInfo['worksheetName'] = (string) $xml->value;
  112. } elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) {
  113. $xml->read(); // Move onto the value node
  114. $tmpInfo['lastColumnIndex'] = (int) $xml->value;
  115. $tmpInfo['totalColumns'] = (int) $xml->value + 1;
  116. } elseif ($xml->name == 'gnm:MaxRow' && $xml->nodeType == XMLReader::ELEMENT) {
  117. $xml->read(); // Move onto the value node
  118. $tmpInfo['totalRows'] = (int) $xml->value + 1;
  119. break;
  120. }
  121. }
  122. $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
  123. $worksheetInfo[] = $tmpInfo;
  124. }
  125. }
  126. return $worksheetInfo;
  127. }
  128. /**
  129. * @param string $filename
  130. *
  131. * @return string
  132. */
  133. private function gzfileGetContents($filename)
  134. {
  135. $file = @gzopen($filename, 'rb');
  136. $data = '';
  137. if ($file !== false) {
  138. while (!gzeof($file)) {
  139. $data .= gzread($file, 1024);
  140. }
  141. gzclose($file);
  142. }
  143. return $data;
  144. }
  145. /**
  146. * Loads Spreadsheet from file.
  147. *
  148. * @param string $pFilename
  149. *
  150. * @throws Exception
  151. *
  152. * @return Spreadsheet
  153. */
  154. public function load($pFilename)
  155. {
  156. // Create new Spreadsheet
  157. $spreadsheet = new Spreadsheet();
  158. // Load into this instance
  159. return $this->loadIntoExisting($pFilename, $spreadsheet);
  160. }
  161. /**
  162. * Loads from file into Spreadsheet instance.
  163. *
  164. * @param string $pFilename
  165. * @param Spreadsheet $spreadsheet
  166. *
  167. * @throws Exception
  168. *
  169. * @return Spreadsheet
  170. */
  171. public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
  172. {
  173. File::assertFile($pFilename);
  174. $gFileData = $this->gzfileGetContents($pFilename);
  175. $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
  176. $namespacesMeta = $xml->getNamespaces(true);
  177. $gnmXML = $xml->children($namespacesMeta['gnm']);
  178. $docProps = $spreadsheet->getProperties();
  179. // Document Properties are held differently, depending on the version of Gnumeric
  180. if (isset($namespacesMeta['office'])) {
  181. $officeXML = $xml->children($namespacesMeta['office']);
  182. $officeDocXML = $officeXML->{'document-meta'};
  183. $officeDocMetaXML = $officeDocXML->meta;
  184. foreach ($officeDocMetaXML as $officePropertyData) {
  185. $officePropertyDC = [];
  186. if (isset($namespacesMeta['dc'])) {
  187. $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
  188. }
  189. foreach ($officePropertyDC as $propertyName => $propertyValue) {
  190. $propertyValue = (string) $propertyValue;
  191. switch ($propertyName) {
  192. case 'title':
  193. $docProps->setTitle(trim($propertyValue));
  194. break;
  195. case 'subject':
  196. $docProps->setSubject(trim($propertyValue));
  197. break;
  198. case 'creator':
  199. $docProps->setCreator(trim($propertyValue));
  200. $docProps->setLastModifiedBy(trim($propertyValue));
  201. break;
  202. case 'date':
  203. $creationDate = strtotime(trim($propertyValue));
  204. $docProps->setCreated($creationDate);
  205. $docProps->setModified($creationDate);
  206. break;
  207. case 'description':
  208. $docProps->setDescription(trim($propertyValue));
  209. break;
  210. }
  211. }
  212. $officePropertyMeta = [];
  213. if (isset($namespacesMeta['meta'])) {
  214. $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
  215. }
  216. foreach ($officePropertyMeta as $propertyName => $propertyValue) {
  217. $attributes = $propertyValue->attributes($namespacesMeta['meta']);
  218. $propertyValue = (string) $propertyValue;
  219. switch ($propertyName) {
  220. case 'keyword':
  221. $docProps->setKeywords(trim($propertyValue));
  222. break;
  223. case 'initial-creator':
  224. $docProps->setCreator(trim($propertyValue));
  225. $docProps->setLastModifiedBy(trim($propertyValue));
  226. break;
  227. case 'creation-date':
  228. $creationDate = strtotime(trim($propertyValue));
  229. $docProps->setCreated($creationDate);
  230. $docProps->setModified($creationDate);
  231. break;
  232. case 'user-defined':
  233. list(, $attrName) = explode(':', $attributes['name']);
  234. switch ($attrName) {
  235. case 'publisher':
  236. $docProps->setCompany(trim($propertyValue));
  237. break;
  238. case 'category':
  239. $docProps->setCategory(trim($propertyValue));
  240. break;
  241. case 'manager':
  242. $docProps->setManager(trim($propertyValue));
  243. break;
  244. }
  245. break;
  246. }
  247. }
  248. }
  249. } elseif (isset($gnmXML->Summary)) {
  250. foreach ($gnmXML->Summary->Item as $summaryItem) {
  251. $propertyName = $summaryItem->name;
  252. $propertyValue = $summaryItem->{'val-string'};
  253. switch ($propertyName) {
  254. case 'title':
  255. $docProps->setTitle(trim($propertyValue));
  256. break;
  257. case 'comments':
  258. $docProps->setDescription(trim($propertyValue));
  259. break;
  260. case 'keywords':
  261. $docProps->setKeywords(trim($propertyValue));
  262. break;
  263. case 'category':
  264. $docProps->setCategory(trim($propertyValue));
  265. break;
  266. case 'manager':
  267. $docProps->setManager(trim($propertyValue));
  268. break;
  269. case 'author':
  270. $docProps->setCreator(trim($propertyValue));
  271. $docProps->setLastModifiedBy(trim($propertyValue));
  272. break;
  273. case 'company':
  274. $docProps->setCompany(trim($propertyValue));
  275. break;
  276. }
  277. }
  278. }
  279. $worksheetID = 0;
  280. foreach ($gnmXML->Sheets->Sheet as $sheet) {
  281. $worksheetName = (string) $sheet->Name;
  282. if ((isset($this->loadSheetsOnly)) && (!in_array($worksheetName, $this->loadSheetsOnly))) {
  283. continue;
  284. }
  285. $maxRow = $maxCol = 0;
  286. // Create new Worksheet
  287. $spreadsheet->createSheet();
  288. $spreadsheet->setActiveSheetIndex($worksheetID);
  289. // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
  290. // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
  291. // name in line with the formula, not the reverse
  292. $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
  293. if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) {
  294. if (isset($sheet->PrintInformation->Margins)) {
  295. foreach ($sheet->PrintInformation->Margins->children('gnm', true) as $key => $margin) {
  296. $marginAttributes = $margin->attributes();
  297. $marginSize = 72 / 100; // Default
  298. switch ($marginAttributes['PrefUnit']) {
  299. case 'mm':
  300. $marginSize = (int) ($marginAttributes['Points']) / 100;
  301. break;
  302. }
  303. switch ($key) {
  304. case 'top':
  305. $spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize);
  306. break;
  307. case 'bottom':
  308. $spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize);
  309. break;
  310. case 'left':
  311. $spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize);
  312. break;
  313. case 'right':
  314. $spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize);
  315. break;
  316. case 'header':
  317. $spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize);
  318. break;
  319. case 'footer':
  320. $spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
  321. break;
  322. }
  323. }
  324. }
  325. }
  326. foreach ($sheet->Cells->Cell as $cell) {
  327. $cellAttributes = $cell->attributes();
  328. $row = (int) $cellAttributes->Row + 1;
  329. $column = (int) $cellAttributes->Col;
  330. if ($row > $maxRow) {
  331. $maxRow = $row;
  332. }
  333. if ($column > $maxCol) {
  334. $maxCol = $column;
  335. }
  336. $column = Coordinate::stringFromColumnIndex($column + 1);
  337. // Read cell?
  338. if ($this->getReadFilter() !== null) {
  339. if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) {
  340. continue;
  341. }
  342. }
  343. $ValueType = $cellAttributes->ValueType;
  344. $ExprID = (string) $cellAttributes->ExprID;
  345. $type = DataType::TYPE_FORMULA;
  346. if ($ExprID > '') {
  347. if (((string) $cell) > '') {
  348. $this->expressions[$ExprID] = [
  349. 'column' => $cellAttributes->Col,
  350. 'row' => $cellAttributes->Row,
  351. 'formula' => (string) $cell,
  352. ];
  353. } else {
  354. $expression = $this->expressions[$ExprID];
  355. $cell = $this->referenceHelper->updateFormulaReferences(
  356. $expression['formula'],
  357. 'A1',
  358. $cellAttributes->Col - $expression['column'],
  359. $cellAttributes->Row - $expression['row'],
  360. $worksheetName
  361. );
  362. }
  363. $type = DataType::TYPE_FORMULA;
  364. } else {
  365. switch ($ValueType) {
  366. case '10': // NULL
  367. $type = DataType::TYPE_NULL;
  368. break;
  369. case '20': // Boolean
  370. $type = DataType::TYPE_BOOL;
  371. $cell = $cell == 'TRUE';
  372. break;
  373. case '30': // Integer
  374. $cell = (int) $cell;
  375. // Excel 2007+ doesn't differentiate between integer and float, so set the value and dropthru to the next (numeric) case
  376. // no break
  377. case '40': // Float
  378. $type = DataType::TYPE_NUMERIC;
  379. break;
  380. case '50': // Error
  381. $type = DataType::TYPE_ERROR;
  382. break;
  383. case '60': // String
  384. $type = DataType::TYPE_STRING;
  385. break;
  386. case '70': // Cell Range
  387. case '80': // Array
  388. }
  389. }
  390. $spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type);
  391. }
  392. if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
  393. foreach ($sheet->Objects->children('gnm', true) as $key => $comment) {
  394. $commentAttributes = $comment->attributes();
  395. // Only comment objects are handled at the moment
  396. if ($commentAttributes->Text) {
  397. $spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text));
  398. }
  399. }
  400. }
  401. foreach ($sheet->Styles->StyleRegion as $styleRegion) {
  402. $styleAttributes = $styleRegion->attributes();
  403. if (($styleAttributes['startRow'] <= $maxRow) &&
  404. ($styleAttributes['startCol'] <= $maxCol)) {
  405. $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
  406. $startRow = $styleAttributes['startRow'] + 1;
  407. $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
  408. $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
  409. $endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow'];
  410. $endRow += 1;
  411. $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
  412. $styleAttributes = $styleRegion->Style->attributes();
  413. // We still set the number format mask for date/time values, even if readDataOnly is true
  414. if ((!$this->readDataOnly) ||
  415. (Date::isDateTimeFormatCode((string) $styleAttributes['Format']))) {
  416. $styleArray = [];
  417. $styleArray['numberFormat']['formatCode'] = (string) $styleAttributes['Format'];
  418. // If readDataOnly is false, we set all formatting information
  419. if (!$this->readDataOnly) {
  420. switch ($styleAttributes['HAlign']) {
  421. case '1':
  422. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL;
  423. break;
  424. case '2':
  425. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT;
  426. break;
  427. case '4':
  428. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT;
  429. break;
  430. case '8':
  431. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER;
  432. break;
  433. case '16':
  434. case '64':
  435. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS;
  436. break;
  437. case '32':
  438. $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY;
  439. break;
  440. }
  441. switch ($styleAttributes['VAlign']) {
  442. case '1':
  443. $styleArray['alignment']['vertical'] = Alignment::VERTICAL_TOP;
  444. break;
  445. case '2':
  446. $styleArray['alignment']['vertical'] = Alignment::VERTICAL_BOTTOM;
  447. break;
  448. case '4':
  449. $styleArray['alignment']['vertical'] = Alignment::VERTICAL_CENTER;
  450. break;
  451. case '8':
  452. $styleArray['alignment']['vertical'] = Alignment::VERTICAL_JUSTIFY;
  453. break;
  454. }
  455. $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
  456. $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
  457. $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
  458. $RGB = self::parseGnumericColour($styleAttributes['Fore']);
  459. $styleArray['font']['color']['rgb'] = $RGB;
  460. $RGB = self::parseGnumericColour($styleAttributes['Back']);
  461. $shade = $styleAttributes['Shade'];
  462. if (($RGB != '000000') || ($shade != '0')) {
  463. $styleArray['fill']['color']['rgb'] = $styleArray['fill']['startColor']['rgb'] = $RGB;
  464. $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
  465. $styleArray['fill']['endColor']['rgb'] = $RGB2;
  466. switch ($shade) {
  467. case '1':
  468. $styleArray['fill']['fillType'] = Fill::FILL_SOLID;
  469. break;
  470. case '2':
  471. $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_LINEAR;
  472. break;
  473. case '3':
  474. $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_PATH;
  475. break;
  476. case '4':
  477. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKDOWN;
  478. break;
  479. case '5':
  480. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRAY;
  481. break;
  482. case '6':
  483. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRID;
  484. break;
  485. case '7':
  486. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKHORIZONTAL;
  487. break;
  488. case '8':
  489. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKTRELLIS;
  490. break;
  491. case '9':
  492. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKUP;
  493. break;
  494. case '10':
  495. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKVERTICAL;
  496. break;
  497. case '11':
  498. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY0625;
  499. break;
  500. case '12':
  501. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY125;
  502. break;
  503. case '13':
  504. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTDOWN;
  505. break;
  506. case '14':
  507. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRAY;
  508. break;
  509. case '15':
  510. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRID;
  511. break;
  512. case '16':
  513. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTHORIZONTAL;
  514. break;
  515. case '17':
  516. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTTRELLIS;
  517. break;
  518. case '18':
  519. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTUP;
  520. break;
  521. case '19':
  522. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTVERTICAL;
  523. break;
  524. case '20':
  525. $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_MEDIUMGRAY;
  526. break;
  527. }
  528. }
  529. $fontAttributes = $styleRegion->Style->Font->attributes();
  530. $styleArray['font']['name'] = (string) $styleRegion->Style->Font;
  531. $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
  532. $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
  533. $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
  534. $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
  535. switch ($fontAttributes['Underline']) {
  536. case '1':
  537. $styleArray['font']['underline'] = Font::UNDERLINE_SINGLE;
  538. break;
  539. case '2':
  540. $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLE;
  541. break;
  542. case '3':
  543. $styleArray['font']['underline'] = Font::UNDERLINE_SINGLEACCOUNTING;
  544. break;
  545. case '4':
  546. $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLEACCOUNTING;
  547. break;
  548. default:
  549. $styleArray['font']['underline'] = Font::UNDERLINE_NONE;
  550. break;
  551. }
  552. switch ($fontAttributes['Script']) {
  553. case '1':
  554. $styleArray['font']['superscript'] = true;
  555. break;
  556. case '-1':
  557. $styleArray['font']['subscript'] = true;
  558. break;
  559. }
  560. if (isset($styleRegion->Style->StyleBorder)) {
  561. if (isset($styleRegion->Style->StyleBorder->Top)) {
  562. $styleArray['borders']['top'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Top->attributes());
  563. }
  564. if (isset($styleRegion->Style->StyleBorder->Bottom)) {
  565. $styleArray['borders']['bottom'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Bottom->attributes());
  566. }
  567. if (isset($styleRegion->Style->StyleBorder->Left)) {
  568. $styleArray['borders']['left'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Left->attributes());
  569. }
  570. if (isset($styleRegion->Style->StyleBorder->Right)) {
  571. $styleArray['borders']['right'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Right->attributes());
  572. }
  573. if ((isset($styleRegion->Style->StyleBorder->Diagonal)) && (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}))) {
  574. $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
  575. $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
  576. } elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) {
  577. $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
  578. $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
  579. } elseif (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'})) {
  580. $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}->attributes());
  581. $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
  582. }
  583. }
  584. if (isset($styleRegion->Style->HyperLink)) {
  585. // TO DO
  586. $hyperlink = $styleRegion->Style->HyperLink->attributes();
  587. }
  588. }
  589. $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
  590. }
  591. }
  592. }
  593. if ((!$this->readDataOnly) && (isset($sheet->Cols))) {
  594. // Column Widths
  595. $columnAttributes = $sheet->Cols->attributes();
  596. $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
  597. $c = 0;
  598. foreach ($sheet->Cols->ColInfo as $columnOverride) {
  599. $columnAttributes = $columnOverride->attributes();
  600. $column = $columnAttributes['No'];
  601. $columnWidth = $columnAttributes['Unit'] / 5.4;
  602. $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1');
  603. $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1;
  604. while ($c < $column) {
  605. $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
  606. ++$c;
  607. }
  608. while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
  609. $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
  610. if ($hidden) {
  611. $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
  612. }
  613. ++$c;
  614. }
  615. }
  616. while ($c <= $maxCol) {
  617. $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
  618. ++$c;
  619. }
  620. }
  621. if ((!$this->readDataOnly) && (isset($sheet->Rows))) {
  622. // Row Heights
  623. $rowAttributes = $sheet->Rows->attributes();
  624. $defaultHeight = $rowAttributes['DefaultSizePts'];
  625. $r = 0;
  626. foreach ($sheet->Rows->RowInfo as $rowOverride) {
  627. $rowAttributes = $rowOverride->attributes();
  628. $row = $rowAttributes['No'];
  629. $rowHeight = $rowAttributes['Unit'];
  630. $hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1');
  631. $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1;
  632. while ($r < $row) {
  633. ++$r;
  634. $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
  635. }
  636. while (($r < ($row + $rowCount)) && ($r < $maxRow)) {
  637. ++$r;
  638. $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
  639. if ($hidden) {
  640. $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false);
  641. }
  642. }
  643. }
  644. while ($r < $maxRow) {
  645. ++$r;
  646. $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
  647. }
  648. }
  649. // Handle Merged Cells in this worksheet
  650. if (isset($sheet->MergedRegions)) {
  651. foreach ($sheet->MergedRegions->Merge as $mergeCells) {
  652. if (strpos($mergeCells, ':') !== false) {
  653. $spreadsheet->getActiveSheet()->mergeCells($mergeCells);
  654. }
  655. }
  656. }
  657. ++$worksheetID;
  658. }
  659. // Loop through definedNames (global named ranges)
  660. if (isset($gnmXML->Names)) {
  661. foreach ($gnmXML->Names->Name as $namedRange) {
  662. $name = (string) $namedRange->name;
  663. $range = (string) $namedRange->value;
  664. if (stripos($range, '#REF!') !== false) {
  665. continue;
  666. }
  667. $range = Worksheet::extractSheetTitle($range, true);
  668. $range[0] = trim($range[0], "'");
  669. if ($worksheet = $spreadsheet->getSheetByName($range[0])) {
  670. $extractedRange = str_replace('$', '', $range[1]);
  671. $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange));
  672. }
  673. }
  674. }
  675. // Return
  676. return $spreadsheet;
  677. }
  678. private static function parseBorderAttributes($borderAttributes)
  679. {
  680. $styleArray = [];
  681. if (isset($borderAttributes['Color'])) {
  682. $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
  683. }
  684. switch ($borderAttributes['Style']) {
  685. case '0':
  686. $styleArray['borderStyle'] = Border::BORDER_NONE;
  687. break;
  688. case '1':
  689. $styleArray['borderStyle'] = Border::BORDER_THIN;
  690. break;
  691. case '2':
  692. $styleArray['borderStyle'] = Border::BORDER_MEDIUM;
  693. break;
  694. case '3':
  695. $styleArray['borderStyle'] = Border::BORDER_SLANTDASHDOT;
  696. break;
  697. case '4':
  698. $styleArray['borderStyle'] = Border::BORDER_DASHED;
  699. break;
  700. case '5':
  701. $styleArray['borderStyle'] = Border::BORDER_THICK;
  702. break;
  703. case '6':
  704. $styleArray['borderStyle'] = Border::BORDER_DOUBLE;
  705. break;
  706. case '7':
  707. $styleArray['borderStyle'] = Border::BORDER_DOTTED;
  708. break;
  709. case '8':
  710. $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHED;
  711. break;
  712. case '9':
  713. $styleArray['borderStyle'] = Border::BORDER_DASHDOT;
  714. break;
  715. case '10':
  716. $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOT;
  717. break;
  718. case '11':
  719. $styleArray['borderStyle'] = Border::BORDER_DASHDOTDOT;
  720. break;
  721. case '12':
  722. $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
  723. break;
  724. case '13':
  725. $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
  726. break;
  727. }
  728. return $styleArray;
  729. }
  730. private function parseRichText($is)
  731. {
  732. $value = new RichText();
  733. $value->createText($is);
  734. return $value;
  735. }
  736. private static function parseGnumericColour($gnmColour)
  737. {
  738. list($gnmR, $gnmG, $gnmB) = explode(':', $gnmColour);
  739. $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
  740. $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
  741. $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
  742. return $gnmR . $gnmG . $gnmB;
  743. }
  744. }