Completed
Push — master ( d4e57b...3e0afd )
by Adrien
02:47
created

StyleHelper   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 11
Bugs 2 Features 4
Metric Value
wmc 31
c 11
b 2
f 4
lcom 1
cbo 5
dl 0
loc 330
ccs 131
cts 131
cp 1
rs 9.8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A registerStyle() 0 7 1
B registerFill() 0 27 3
B registerBorder() 0 24 3
A shouldApplyStyleOnEmptyCell() 0 7 4
A getStylesXMLFileContent() 0 20 1
B getFontsSectionContent() 0 32 6
B getFillsSectionContent() 0 25 2
B getBordersSectionContent() 0 36 4
A getCellStyleXfsSectionContent() 0 8 1
B getCellXfsSectionContent() 0 32 5
A getCellStylesSectionContent() 0 8 1
1
<?php
2
3
namespace Box\Spout\Writer\XLSX\Helper;
4
5
use Box\Spout\Writer\Common\Helper\AbstractStyleHelper;
6
use Box\Spout\Writer\Style\Color;
7
use Box\Spout\Writer\Style\Style;
8
9
/**
10
 * Class StyleHelper
11
 * This class provides helper functions to manage styles
12
 *
13
 * @package Box\Spout\Writer\XLSX\Helper
14
 */
15
class StyleHelper extends AbstractStyleHelper
16
{
17
    /**
18
     * @var array
19
     */
20
    protected $registeredFills = [];
21
22
    /**
23
     * @var array [STYLE_ID] => [FILL_ID] maps a style to a fill declaration
24
     */
25
    protected $styleIdToFillMappingTable = [];
26
27
    /**
28
     * Excel preserves two default fills with index 0 and 1
29
     * Since Excel is the dominant vendor - we play along here
30
     *
31
     * @var int The fill index counter for custom fills.
32
     */
33
    protected $fillIndex = 2;
34
35
    /**
36
     * @var array
37
     */
38
    protected $registeredBorders = [];
39
40
    /**
41
     * @var array [STYLE_ID] => [BORDER_ID] maps a style to a border declaration
42
     */
43
    protected $styleIdToBorderMappingTable = [];
44
45
    /**
46
     * XLSX specific operations on the registered styles
47
     *
48
     * @param \Box\Spout\Writer\Style\Style $style
49
     * @return \Box\Spout\Writer\Style\Style
50
     */
51 135
    public function registerStyle($style)
52
    {
53 135
        $registeredStyle = parent::registerStyle($style);
54 135
        $this->registerFill($registeredStyle);
55 135
        $this->registerBorder($registeredStyle);
56 135
        return $registeredStyle;
57
    }
58
59
    /**
60
     * Register a fill definition
61
     *
62
     * @param \Box\Spout\Writer\Style\Style $style
63
     */
64 135
    protected function registerFill($style)
65
    {
66 135
        $styleId = $style->getId();
67
68
        // Currently - only solid backgrounds are supported
69
        // so $backgroundColor is a scalar value (RGB Color)
70 135
        $backgroundColor = $style->getBackgroundColor();
71
72 135
        if ($backgroundColor) {
73 12
            $isBackgroundColorRegistered = isset($this->registeredFills[$backgroundColor]);
74
75
            // We need to track the already registered background definitions
76 12
            if ($isBackgroundColorRegistered) {
77 3
                $registeredStyleId = $this->registeredFills[$backgroundColor];
78 3
                $registeredFillId = $this->styleIdToFillMappingTable[$registeredStyleId];
79 3
                $this->styleIdToFillMappingTable[$styleId] = $registeredFillId;
80 3
            } else {
81 12
                $this->registeredFills[$backgroundColor] = $styleId;
82 12
                $this->styleIdToFillMappingTable[$styleId] = $this->fillIndex++;
83
            }
84
85 12
        } else {
86
            // The fillId maps a style to a fill declaration
87
            // When there is no background color definition - we default to 0
88 135
            $this->styleIdToFillMappingTable[$styleId] = 0;
89
        }
90 135
    }
91
92
    /**
93
     * Register a border definition
94
     *
95
     * @param \Box\Spout\Writer\Style\Style $style
96
     */
97 135
    protected function registerBorder($style)
98
    {
99 135
        $styleId = $style->getId();
100
101 135
        if ($style->shouldApplyBorder()) {
102 15
            $border = $style->getBorder();
103 15
            $serializedBorder = serialize($border);
104
105 15
            $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]);
106
107 15
            if ($isBorderAlreadyRegistered) {
108 3
                $registeredStyleId = $this->registeredBorders[$serializedBorder];
109 3
                $registeredBorderId = $this->styleIdToBorderMappingTable[$registeredStyleId];
110 3
                $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId;
111 3
            } else {
112 15
                $this->registeredBorders[$serializedBorder] = $styleId;
113 15
                $this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders);
114
            }
115
116 15
        } else {
117
            // If no border should be applied - the mapping is the default border: 0
118 135
            $this->styleIdToBorderMappingTable[$styleId] = 0;
119
        }
120 135
    }
121
122
123
    /**
124
     * For empty cells, we can specify a style or not. If no style are specified,
125
     * then the software default will be applied. But sometimes, it may be useful
126
     * to override this default style, for instance if the cell should have a
127
     * background color different than the default one or some borders
128
     * (fonts property don't really matter here).
129
     *
130
     * @param int $styleId
131
     * @return bool Whether the cell should define a custom style
132
     */
133 9
    public function shouldApplyStyleOnEmptyCell($styleId)
134
    {
135 9
        $hasStyleCustomFill = (isset($this->styleIdToFillMappingTable[$styleId]) && $this->styleIdToFillMappingTable[$styleId] !== 0);
136 9
        $hasStyleCustomBorders = (isset($this->styleIdToBorderMappingTable[$styleId]) && $this->styleIdToBorderMappingTable[$styleId] !== 0);
137
138 9
        return ($hasStyleCustomFill || $hasStyleCustomBorders);
139
    }
140
141
142
    /**
143
     * Returns the content of the "styles.xml" file, given a list of styles.
144
     *
145
     * @return string
146
     */
147 87
    public function getStylesXMLFileContent()
148
    {
149
        $content = <<<EOD
150
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
151
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
152 87
EOD;
153
154 87
        $content .= $this->getFontsSectionContent();
155 87
        $content .= $this->getFillsSectionContent();
156 87
        $content .= $this->getBordersSectionContent();
157 87
        $content .= $this->getCellStyleXfsSectionContent();
158 87
        $content .= $this->getCellXfsSectionContent();
159 87
        $content .= $this->getCellStylesSectionContent();
160
161
        $content .= <<<EOD
162
</styleSheet>
163 87
EOD;
164
165 87
        return $content;
166
    }
167
168
    /**
169
     * Returns the content of the "<fonts>" section.
170
     *
171
     * @return string
172
     */
173 87
    protected function getFontsSectionContent()
174
    {
175 87
        $content = '<fonts count="' . count($this->styleIdToStyleMappingTable) . '">';
176
177
        /** @var \Box\Spout\Writer\Style\Style $style */
178 87
        foreach ($this->getRegisteredStyles() as $style) {
179 87
            $content .= '<font>';
180
181 87
            $content .= '<sz val="' . $style->getFontSize() . '"/>';
182 87
            $content .= '<color rgb="' . Color::toARGB($style->getFontColor()) . '"/>';
183 87
            $content .= '<name val="' . $style->getFontName() . '"/>';
184
185 87
            if ($style->isFontBold()) {
186 18
                $content .= '<b/>';
187 18
            }
188 87
            if ($style->isFontItalic()) {
189 3
                $content .= '<i/>';
190 3
            }
191 87
            if ($style->isFontUnderline()) {
192 3
                $content .= '<u/>';
193 3
            }
194 87
            if ($style->isFontStrikethrough()) {
195 3
                $content .= '<strike/>';
196 3
            }
197
198 87
            $content .= '</font>';
199 87
        }
200
201 87
        $content .= '</fonts>';
202
203 87
        return $content;
204
    }
205
206
    /**
207
     * Returns the content of the "<fills>" section.
208
     *
209
     * @return string
210
     */
211 87
    protected function getFillsSectionContent()
212
    {
213
        // Excel reserves two default fills
214 87
        $fillsCount = count($this->registeredFills) + 2;
215 87
        $content = sprintf('<fills count="%d">', $fillsCount);
216
217 87
        $content .= '<fill><patternFill patternType="none"/></fill>';
218 87
        $content .= '<fill><patternFill patternType="gray125"/></fill>';
219
220
        // The other fills are actually registered by setting a background color
221 87
        foreach ($this->registeredFills as $styleId) {
222
            /** @var Style $style */
223 9
            $style = $this->styleIdToStyleMappingTable[$styleId];
224
225 9
            $backgroundColor = $style->getBackgroundColor();
226 9
            $content .= sprintf(
227 9
                '<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
228
                $backgroundColor
229 9
            );
230 87
        }
231
232 87
        $content .= '</fills>';
233
234 87
        return $content;
235
    }
236
237
    /**
238
     * Returns the content of the "<borders>" section.
239
     *
240
     * @return string
241
     */
242 87
    protected function getBordersSectionContent()
243
    {
244
245
        // There is one default border with index 0
246 87
        $borderCount = count($this->registeredBorders) + 1;
247
248 87
        $content = '<borders count="' . $borderCount . '">';
249
250
        // Default border starting at index 0
251 87
        $content .= '<border><left/><right/><top/><bottom/></border>';
252
253 87
        foreach ($this->registeredBorders as $styleId) {
254
            /** @var \Box\Spout\Writer\Style\Style $style */
255 12
            $style = $this->styleIdToStyleMappingTable[$styleId];
256 12
            $border = $style->getBorder();
257 12
            $content .= '<border>';
258
259
            // @link https://github.com/box/spout/issues/271
260 12
            $sortOrder = ['left', 'right', 'top', 'bottom'];
261
262 12
            foreach ($sortOrder as $partName) {
263 12
                if ($border->hasPart($partName)) {
264
                    /** @var $part \Box\Spout\Writer\Style\BorderPart */
265 12
                    $part = $border->getPart($partName);
266 12
                    $content .= BorderHelper::serializeBorderPart($part);
267 12
                }
268
269 12
            }
270
271 12
            $content .= '</border>';
272 87
        }
273
274 87
        $content .= '</borders>';
275
276 87
        return $content;
277
    }
278
279
    /**
280
     * Returns the content of the "<cellStyleXfs>" section.
281
     *
282
     * @return string
283
     */
284 87
    protected function getCellStyleXfsSectionContent()
285
    {
286
        return <<<EOD
287
<cellStyleXfs count="1">
288
    <xf borderId="0" fillId="0" fontId="0" numFmtId="0"/>
289
</cellStyleXfs>
290 87
EOD;
291
    }
292
293
    /**
294
     * Returns the content of the "<cellXfs>" section.
295
     *
296
     * @return string
297
     */
298 87
    protected function getCellXfsSectionContent()
299
    {
300 87
        $registeredStyles = $this->getRegisteredStyles();
301
302 87
        $content = '<cellXfs count="' . count($registeredStyles) . '">';
303
304 87
        foreach ($registeredStyles as $style) {
305 87
            $styleId = $style->getId();
306 87
            $fillId = $this->styleIdToFillMappingTable[$styleId];
307 87
            $borderId = $this->styleIdToBorderMappingTable[$styleId];
308
309 87
            $content .= '<xf numFmtId="0" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"';
310
311 87
            if ($style->shouldApplyFont()) {
312 87
                $content .= ' applyFont="1"';
313 87
            }
314
315 87
            $content .= sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0);
316
317 87
            if ($style->shouldWrapText()) {
318 6
                $content .= ' applyAlignment="1">';
319 6
                $content .= '<alignment wrapText="1"/>';
320 6
                $content .= '</xf>';
321 6
            } else {
322 87
                $content .= '/>';
323
            }
324 87
        }
325
326 87
        $content .= '</cellXfs>';
327
328 87
        return $content;
329
    }
330
331
    /**
332
     * Returns the content of the "<cellStyles>" section.
333
     *
334
     * @return string
335
     */
336 87
    protected function getCellStylesSectionContent()
337
    {
338
        return <<<EOD
339
<cellStyles count="1">
340
    <cellStyle builtinId="0" name="Normal" xfId="0"/>
341
</cellStyles>
342 87
EOD;
343
    }
344
}
345