Gnumeric.php 38 KB

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