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