Style.php 18 KB

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