Style.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2014 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Style
  23. * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version 1.8.0, 2014-03-02
  26. */
  27. /**
  28. * PHPExcel_Style
  29. *
  30. * @category PHPExcel
  31. * @package PHPExcel_Style
  32. * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
  33. */
  34. class PHPExcel_Style extends PHPExcel_Style_Supervisor implements PHPExcel_IComparable
  35. {
  36. /**
  37. * Font
  38. *
  39. * @var PHPExcel_Style_Font
  40. */
  41. protected $_font;
  42. /**
  43. * Fill
  44. *
  45. * @var PHPExcel_Style_Fill
  46. */
  47. protected $_fill;
  48. /**
  49. * Borders
  50. *
  51. * @var PHPExcel_Style_Borders
  52. */
  53. protected $_borders;
  54. /**
  55. * Alignment
  56. *
  57. * @var PHPExcel_Style_Alignment
  58. */
  59. protected $_alignment;
  60. /**
  61. * Number Format
  62. *
  63. * @var PHPExcel_Style_NumberFormat
  64. */
  65. protected $_numberFormat;
  66. /**
  67. * Conditional styles
  68. *
  69. * @var PHPExcel_Style_Conditional[]
  70. */
  71. protected $_conditionalStyles;
  72. /**
  73. * Protection
  74. *
  75. * @var PHPExcel_Style_Protection
  76. */
  77. protected $_protection;
  78. /**
  79. * Index of style in collection. Only used for real style.
  80. *
  81. * @var int
  82. */
  83. protected $_index;
  84. /**
  85. * Use Quote Prefix when displaying in cell editor. Only used for real style.
  86. *
  87. * @var boolean
  88. */
  89. protected $_quotePrefix = false;
  90. /**
  91. * Create a new PHPExcel_Style
  92. *
  93. * @param boolean $isSupervisor Flag indicating if this is a supervisor or not
  94. * Leave this value at default unless you understand exactly what
  95. * its ramifications are
  96. * @param boolean $isConditional Flag indicating if this is a conditional style or not
  97. * Leave this value at default unless you understand exactly what
  98. * its ramifications are
  99. */
  100. public function __construct($isSupervisor = false, $isConditional = false)
  101. {
  102. // Supervisor?
  103. $this->_isSupervisor = $isSupervisor;
  104. // Initialise values
  105. $this->_conditionalStyles = array();
  106. $this->_font = new PHPExcel_Style_Font($isSupervisor, $isConditional);
  107. $this->_fill = new PHPExcel_Style_Fill($isSupervisor, $isConditional);
  108. $this->_borders = new PHPExcel_Style_Borders($isSupervisor, $isConditional);
  109. $this->_alignment = new PHPExcel_Style_Alignment($isSupervisor, $isConditional);
  110. $this->_numberFormat = new PHPExcel_Style_NumberFormat($isSupervisor, $isConditional);
  111. $this->_protection = new PHPExcel_Style_Protection($isSupervisor, $isConditional);
  112. // bind parent if we are a supervisor
  113. if ($isSupervisor) {
  114. $this->_font->bindParent($this);
  115. $this->_fill->bindParent($this);
  116. $this->_borders->bindParent($this);
  117. $this->_alignment->bindParent($this);
  118. $this->_numberFormat->bindParent($this);
  119. $this->_protection->bindParent($this);
  120. }
  121. }
  122. /**
  123. * Get the shared style component for the currently active cell in currently active sheet.
  124. * Only used for style supervisor
  125. *
  126. * @return PHPExcel_Style
  127. */
  128. public function getSharedComponent()
  129. {
  130. $activeSheet = $this->getActiveSheet();
  131. $selectedCell = $this->getActiveCell(); // e.g. 'A1'
  132. if ($activeSheet->cellExists($selectedCell)) {
  133. $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
  134. } else {
  135. $xfIndex = 0;
  136. }
  137. return $this->_parent->getCellXfByIndex($xfIndex);
  138. }
  139. /**
  140. * Get parent. Only used for style supervisor
  141. *
  142. * @return PHPExcel
  143. */
  144. public function getParent()
  145. {
  146. return $this->_parent;
  147. }
  148. /**
  149. * Build style array from subcomponents
  150. *
  151. * @param array $array
  152. * @return array
  153. */
  154. public function getStyleArray($array)
  155. {
  156. return array('quotePrefix' => $array);
  157. }
  158. /**
  159. * Apply styles from array
  160. *
  161. * <code>
  162. * $objPHPExcel->getActiveSheet()->getStyle('B2')->applyFromArray(
  163. * array(
  164. * 'font' => array(
  165. * 'name' => 'Arial',
  166. * 'bold' => true,
  167. * 'italic' => false,
  168. * 'underline' => PHPExcel_Style_Font::UNDERLINE_DOUBLE,
  169. * 'strike' => false,
  170. * 'color' => array(
  171. * 'rgb' => '808080'
  172. * )
  173. * ),
  174. * 'borders' => array(
  175. * 'bottom' => array(
  176. * 'style' => PHPExcel_Style_Border::BORDER_DASHDOT,
  177. * 'color' => array(
  178. * 'rgb' => '808080'
  179. * )
  180. * ),
  181. * 'top' => array(
  182. * 'style' => PHPExcel_Style_Border::BORDER_DASHDOT,
  183. * 'color' => array(
  184. * 'rgb' => '808080'
  185. * )
  186. * )
  187. * ),
  188. * 'quotePrefix' => true
  189. * )
  190. * );
  191. * </code>
  192. *
  193. * @param array $pStyles Array containing style information
  194. * @param boolean $pAdvanced Advanced mode for setting borders.
  195. * @throws PHPExcel_Exception
  196. * @return PHPExcel_Style
  197. */
  198. public function applyFromArray($pStyles = null, $pAdvanced = true)
  199. {
  200. if (is_array($pStyles)) {
  201. if ($this->_isSupervisor) {
  202. $pRange = $this->getSelectedCells();
  203. // Uppercase coordinate
  204. $pRange = strtoupper($pRange);
  205. // Is it a cell range or a single cell?
  206. if (strpos($pRange, ':') === false) {
  207. $rangeA = $pRange;
  208. $rangeB = $pRange;
  209. } else {
  210. list($rangeA, $rangeB) = explode(':', $pRange);
  211. }
  212. // Calculate range outer borders
  213. $rangeStart = PHPExcel_Cell::coordinateFromString($rangeA);
  214. $rangeEnd = PHPExcel_Cell::coordinateFromString($rangeB);
  215. // Translate column into index
  216. $rangeStart[0] = PHPExcel_Cell::columnIndexFromString($rangeStart[0]) - 1;
  217. $rangeEnd[0] = PHPExcel_Cell::columnIndexFromString($rangeEnd[0]) - 1;
  218. // Make sure we can loop upwards on rows and columns
  219. if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
  220. $tmp = $rangeStart;
  221. $rangeStart = $rangeEnd;
  222. $rangeEnd = $tmp;
  223. }
  224. // ADVANCED MODE:
  225. if ($pAdvanced && isset($pStyles['borders'])) {
  226. // 'allborders' is a shorthand property for 'outline' and 'inside' and
  227. // it applies to components that have not been set explicitly
  228. if (isset($pStyles['borders']['allborders'])) {
  229. foreach (array('outline', 'inside') as $component) {
  230. if (!isset($pStyles['borders'][$component])) {
  231. $pStyles['borders'][$component] = $pStyles['borders']['allborders'];
  232. }
  233. }
  234. unset($pStyles['borders']['allborders']); // not needed any more
  235. }
  236. // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
  237. // it applies to components that have not been set explicitly
  238. if (isset($pStyles['borders']['outline'])) {
  239. foreach (array('top', 'right', 'bottom', 'left') as $component) {
  240. if (!isset($pStyles['borders'][$component])) {
  241. $pStyles['borders'][$component] = $pStyles['borders']['outline'];
  242. }
  243. }
  244. unset($pStyles['borders']['outline']); // not needed any more
  245. }
  246. // 'inside' is a shorthand property for 'vertical' and 'horizontal'
  247. // it applies to components that have not been set explicitly
  248. if (isset($pStyles['borders']['inside'])) {
  249. foreach (array('vertical', 'horizontal') as $component) {
  250. if (!isset($pStyles['borders'][$component])) {
  251. $pStyles['borders'][$component] = $pStyles['borders']['inside'];
  252. }
  253. }
  254. unset($pStyles['borders']['inside']); // not needed any more
  255. }
  256. // width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
  257. $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3);
  258. $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3);
  259. // loop through up to 3 x 3 = 9 regions
  260. for ($x = 1; $x <= $xMax; ++$x) {
  261. // start column index for region
  262. $colStart = ($x == 3) ?
  263. PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0])
  264. : PHPExcel_Cell::stringFromColumnIndex($rangeStart[0] + $x - 1);
  265. // end column index for region
  266. $colEnd = ($x == 1) ?
  267. PHPExcel_Cell::stringFromColumnIndex($rangeStart[0])
  268. : PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0] - $xMax + $x);
  269. for ($y = 1; $y <= $yMax; ++$y) {
  270. // which edges are touching the region
  271. $edges = array();
  272. // are we at left edge
  273. if ($x == 1) {
  274. $edges[] = 'left';
  275. }
  276. // are we at right edge
  277. if ($x == $xMax) {
  278. $edges[] = 'right';
  279. }
  280. // are we at top edge?
  281. if ($y == 1) {
  282. $edges[] = 'top';
  283. }
  284. // are we at bottom edge?
  285. if ($y == $yMax) {
  286. $edges[] = 'bottom';
  287. }
  288. // start row index for region
  289. $rowStart = ($y == 3) ?
  290. $rangeEnd[1] : $rangeStart[1] + $y - 1;
  291. // end row index for region
  292. $rowEnd = ($y == 1) ?
  293. $rangeStart[1] : $rangeEnd[1] - $yMax + $y;
  294. // build range for region
  295. $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
  296. // retrieve relevant style array for region
  297. $regionStyles = $pStyles;
  298. unset($regionStyles['borders']['inside']);
  299. // what are the inner edges of the region when looking at the selection
  300. $innerEdges = array_diff( array('top', 'right', 'bottom', 'left'), $edges );
  301. // inner edges that are not touching the region should take the 'inside' border properties if they have been set
  302. foreach ($innerEdges as $innerEdge) {
  303. switch ($innerEdge) {
  304. case 'top':
  305. case 'bottom':
  306. // should pick up 'horizontal' border property if set
  307. if (isset($pStyles['borders']['horizontal'])) {
  308. $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal'];
  309. } else {
  310. unset($regionStyles['borders'][$innerEdge]);
  311. }
  312. break;
  313. case 'left':
  314. case 'right':
  315. // should pick up 'vertical' border property if set
  316. if (isset($pStyles['borders']['vertical'])) {
  317. $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical'];
  318. } else {
  319. unset($regionStyles['borders'][$innerEdge]);
  320. }
  321. break;
  322. }
  323. }
  324. // apply region style to region by calling applyFromArray() in simple mode
  325. $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
  326. }
  327. }
  328. return $this;
  329. }
  330. // SIMPLE MODE:
  331. // Selection type, inspect
  332. if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
  333. $selectionType = 'COLUMN';
  334. } else if (preg_match('/^A[0-9]+:XFD[0-9]+$/', $pRange)) {
  335. $selectionType = 'ROW';
  336. } else {
  337. $selectionType = 'CELL';
  338. }
  339. // First loop through columns, rows, or cells to find out which styles are affected by this operation
  340. switch ($selectionType) {
  341. case 'COLUMN':
  342. $oldXfIndexes = array();
  343. for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  344. $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
  345. }
  346. break;
  347. case 'ROW':
  348. $oldXfIndexes = array();
  349. for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  350. if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) {
  351. $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
  352. } else {
  353. $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
  354. }
  355. }
  356. break;
  357. case 'CELL':
  358. $oldXfIndexes = array();
  359. for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  360. for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  361. $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true;
  362. }
  363. }
  364. break;
  365. }
  366. // clone each of the affected styles, apply the style array, and add the new styles to the workbook
  367. $workbook = $this->getActiveSheet()->getParent();
  368. foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
  369. $style = $workbook->getCellXfByIndex($oldXfIndex);
  370. $newStyle = clone $style;
  371. $newStyle->applyFromArray($pStyles);
  372. if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
  373. // there is already such cell Xf in our collection
  374. $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
  375. } else {
  376. // we don't have such a cell Xf, need to add
  377. $workbook->addCellXf($newStyle);
  378. $newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
  379. }
  380. }
  381. // Loop through columns, rows, or cells again and update the XF index
  382. switch ($selectionType) {
  383. case 'COLUMN':
  384. for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  385. $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
  386. $oldXfIndex = $columnDimension->getXfIndex();
  387. $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
  388. }
  389. break;
  390. case 'ROW':
  391. for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  392. $rowDimension = $this->getActiveSheet()->getRowDimension($row);
  393. $oldXfIndex = $rowDimension->getXfIndex() === null ?
  394. 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style
  395. $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
  396. }
  397. break;
  398. case 'CELL':
  399. for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  400. for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  401. $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row);
  402. $oldXfIndex = $cell->getXfIndex();
  403. $cell->setXfIndex($newXfIndexes[$oldXfIndex]);
  404. }
  405. }
  406. break;
  407. }
  408. } else {
  409. // not a supervisor, just apply the style array directly on style object
  410. if (array_key_exists('fill', $pStyles)) {
  411. $this->getFill()->applyFromArray($pStyles['fill']);
  412. }
  413. if (array_key_exists('font', $pStyles)) {
  414. $this->getFont()->applyFromArray($pStyles['font']);
  415. }
  416. if (array_key_exists('borders', $pStyles)) {
  417. $this->getBorders()->applyFromArray($pStyles['borders']);
  418. }
  419. if (array_key_exists('alignment', $pStyles)) {
  420. $this->getAlignment()->applyFromArray($pStyles['alignment']);
  421. }
  422. if (array_key_exists('numberformat', $pStyles)) {
  423. $this->getNumberFormat()->applyFromArray($pStyles['numberformat']);
  424. }
  425. if (array_key_exists('protection', $pStyles)) {
  426. $this->getProtection()->applyFromArray($pStyles['protection']);
  427. }
  428. if (array_key_exists('quotePrefix', $pStyles)) {
  429. $this->_quotePrefix = $pStyles['quotePrefix'];
  430. }
  431. }
  432. } else {
  433. throw new PHPExcel_Exception("Invalid style array passed.");
  434. }
  435. return $this;
  436. }
  437. /**
  438. * Get Fill
  439. *
  440. * @return PHPExcel_Style_Fill
  441. */
  442. public function getFill()
  443. {
  444. return $this->_fill;
  445. }
  446. /**
  447. * Get Font
  448. *
  449. * @return PHPExcel_Style_Font
  450. */
  451. public function getFont()
  452. {
  453. return $this->_font;
  454. }
  455. /**
  456. * Set font
  457. *
  458. * @param PHPExcel_Style_Font $font
  459. * @return PHPExcel_Style
  460. */
  461. public function setFont(PHPExcel_Style_Font $font)
  462. {
  463. $this->_font = $font;
  464. return $this;
  465. }
  466. /**
  467. * Get Borders
  468. *
  469. * @return PHPExcel_Style_Borders
  470. */
  471. public function getBorders()
  472. {
  473. return $this->_borders;
  474. }
  475. /**
  476. * Get Alignment
  477. *
  478. * @return PHPExcel_Style_Alignment
  479. */
  480. public function getAlignment()
  481. {
  482. return $this->_alignment;
  483. }
  484. /**
  485. * Get Number Format
  486. *
  487. * @return PHPExcel_Style_NumberFormat
  488. */
  489. public function getNumberFormat()
  490. {
  491. return $this->_numberFormat;
  492. }
  493. /**
  494. * Get Conditional Styles. Only used on supervisor.
  495. *
  496. * @return PHPExcel_Style_Conditional[]
  497. */
  498. public function getConditionalStyles()
  499. {
  500. return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
  501. }
  502. /**
  503. * Set Conditional Styles. Only used on supervisor.
  504. *
  505. * @param PHPExcel_Style_Conditional[] $pValue Array of condtional styles
  506. * @return PHPExcel_Style
  507. */
  508. public function setConditionalStyles($pValue = null)
  509. {
  510. if (is_array($pValue)) {
  511. $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue);
  512. }
  513. return $this;
  514. }
  515. /**
  516. * Get Protection
  517. *
  518. * @return PHPExcel_Style_Protection
  519. */
  520. public function getProtection()
  521. {
  522. return $this->_protection;
  523. }
  524. /**
  525. * Get quote prefix
  526. *
  527. * @return boolean
  528. */
  529. public function getQuotePrefix()
  530. {
  531. if ($this->_isSupervisor) {
  532. return $this->getSharedComponent()->getQuotePrefix();
  533. }
  534. return $this->_quotePrefix;
  535. }
  536. /**
  537. * Set quote prefix
  538. *
  539. * @param boolean $pValue
  540. */
  541. public function setQuotePrefix($pValue)
  542. {
  543. if ($pValue == '') {
  544. $pValue = false;
  545. }
  546. if ($this->_isSupervisor) {
  547. $styleArray = array('quotePrefix' => $pValue);
  548. $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
  549. } else {
  550. $this->_quotePrefix = (boolean) $pValue;
  551. }
  552. return $this;
  553. }
  554. /**
  555. * Get hash code
  556. *
  557. * @return string Hash code
  558. */
  559. public function getHashCode()
  560. {
  561. $hashConditionals = '';
  562. foreach ($this->_conditionalStyles as $conditional) {
  563. $hashConditionals .= $conditional->getHashCode();
  564. }
  565. return md5(
  566. $this->_fill->getHashCode()
  567. . $this->_font->getHashCode()
  568. . $this->_borders->getHashCode()
  569. . $this->_alignment->getHashCode()
  570. . $this->_numberFormat->getHashCode()
  571. . $hashConditionals
  572. . $this->_protection->getHashCode()
  573. . ($this->_quotePrefix ? 't' : 'f')
  574. . __CLASS__
  575. );
  576. }
  577. /**
  578. * Get own index in style collection
  579. *
  580. * @return int
  581. */
  582. public function getIndex()
  583. {
  584. return $this->_index;
  585. }
  586. /**
  587. * Set own index in style collection
  588. *
  589. * @param int $pValue
  590. */
  591. public function setIndex($pValue)
  592. {
  593. $this->_index = $pValue;
  594. }
  595. }