Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Writer\XLSX\Manager\Style;
6
 
7
use OpenSpout\Common\Entity\Style\BorderPart;
8
use OpenSpout\Common\Entity\Style\Color;
9
use OpenSpout\Common\Entity\Style\Style;
1441 ariadna 10
use OpenSpout\Common\Helper\Escaper\XLSX as XLSXEscaper;
1 efrain 11
use OpenSpout\Writer\Common\Manager\Style\AbstractStyleManager as CommonStyleManager;
12
use OpenSpout\Writer\XLSX\Helper\BorderHelper;
13
 
14
/**
15
 * @internal
16
 *
17
 * @property StyleRegistry $styleRegistry
18
 */
19
final class StyleManager extends CommonStyleManager
20
{
1441 ariadna 21
    /** @var XLSXEscaper Strings escaper */
22
    private readonly XLSXEscaper $stringsEscaper;
23
 
24
    public function __construct(StyleRegistry $styleRegistry, XLSXEscaper $stringsEscaper)
1 efrain 25
    {
26
        parent::__construct($styleRegistry);
1441 ariadna 27
        $this->stringsEscaper = $stringsEscaper;
1 efrain 28
    }
29
 
30
    /**
31
     * For empty cells, we can specify a style or not. If no style are specified,
32
     * then the software default will be applied. But sometimes, it may be useful
33
     * to override this default style, for instance if the cell should have a
34
     * background color different than the default one or some borders
35
     * (fonts property don't really matter here).
36
     *
37
     * @return bool Whether the cell should define a custom style
38
     */
39
    public function shouldApplyStyleOnEmptyCell(?int $styleId): bool
40
    {
41
        if (null === $styleId) {
42
            return false;
43
        }
44
        $associatedFillId = $this->styleRegistry->getFillIdForStyleId($styleId);
45
        $hasStyleCustomFill = (null !== $associatedFillId && 0 !== $associatedFillId);
46
 
47
        $associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId);
48
        $hasStyleCustomBorders = (null !== $associatedBorderId && 0 !== $associatedBorderId);
49
 
50
        $associatedFormatId = $this->styleRegistry->getFormatIdForStyleId($styleId);
51
        $hasStyleCustomFormats = (null !== $associatedFormatId && 0 !== $associatedFormatId);
52
 
53
        return $hasStyleCustomFill || $hasStyleCustomBorders || $hasStyleCustomFormats;
54
    }
55
 
56
    /**
57
     * Returns the content of the "styles.xml" file, given a list of styles.
58
     */
59
    public function getStylesXMLFileContent(): string
60
    {
61
        $content = <<<'EOD'
62
            <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
63
            <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
64
            EOD;
65
 
66
        $content .= $this->getFormatsSectionContent();
67
        $content .= $this->getFontsSectionContent();
68
        $content .= $this->getFillsSectionContent();
69
        $content .= $this->getBordersSectionContent();
70
        $content .= $this->getCellStyleXfsSectionContent();
71
        $content .= $this->getCellXfsSectionContent();
72
        $content .= $this->getCellStylesSectionContent();
73
 
74
        $content .= <<<'EOD'
75
            </styleSheet>
76
            EOD;
77
 
78
        return $content;
79
    }
80
 
81
    /**
82
     * Returns the content of the "<numFmts>" section.
83
     */
84
    private function getFormatsSectionContent(): string
85
    {
86
        $tags = [];
87
        $registeredFormats = $this->styleRegistry->getRegisteredFormats();
88
        foreach ($registeredFormats as $styleId) {
89
            $numFmtId = $this->styleRegistry->getFormatIdForStyleId($styleId);
90
 
91
            // Built-in formats do not need to be declared, skip them
92
            if ($numFmtId < 164) {
93
                continue;
94
            }
95
 
96
            /** @var Style $style */
97
            $style = $this->styleRegistry->getStyleFromStyleId($styleId);
98
            $format = $style->getFormat();
1441 ariadna 99
            $tags[] = '<numFmt numFmtId="'.$numFmtId.'" formatCode="'.$this->stringsEscaper->escape($format).'"/>';
1 efrain 100
        }
101
        $content = '<numFmts count="'.\count($tags).'">';
102
        $content .= implode('', $tags);
103
        $content .= '</numFmts>';
104
 
105
        return $content;
106
    }
107
 
108
    /**
109
     * Returns the content of the "<fonts>" section.
110
     */
111
    private function getFontsSectionContent(): string
112
    {
113
        $registeredStyles = $this->styleRegistry->getRegisteredStyles();
114
 
115
        $content = '<fonts count="'.\count($registeredStyles).'">';
116
 
117
        /** @var Style $style */
118
        foreach ($registeredStyles as $style) {
119
            $content .= '<font>';
120
 
121
            $content .= '<sz val="'.$style->getFontSize().'"/>';
122
            $content .= '<color rgb="'.Color::toARGB($style->getFontColor()).'"/>';
123
            $content .= '<name val="'.$style->getFontName().'"/>';
124
 
125
            if ($style->isFontBold()) {
126
                $content .= '<b/>';
127
            }
128
            if ($style->isFontItalic()) {
129
                $content .= '<i/>';
130
            }
131
            if ($style->isFontUnderline()) {
132
                $content .= '<u/>';
133
            }
134
            if ($style->isFontStrikethrough()) {
135
                $content .= '<strike/>';
136
            }
137
 
138
            $content .= '</font>';
139
        }
140
 
141
        $content .= '</fonts>';
142
 
143
        return $content;
144
    }
145
 
146
    /**
147
     * Returns the content of the "<fills>" section.
148
     */
149
    private function getFillsSectionContent(): string
150
    {
151
        $registeredFills = $this->styleRegistry->getRegisteredFills();
152
 
153
        // Excel reserves two default fills
154
        $fillsCount = \count($registeredFills) + 2;
1441 ariadna 155
        $content = \sprintf('<fills count="%d">', $fillsCount);
1 efrain 156
 
157
        $content .= '<fill><patternFill patternType="none"/></fill>';
158
        $content .= '<fill><patternFill patternType="gray125"/></fill>';
159
 
160
        // The other fills are actually registered by setting a background color
161
        foreach ($registeredFills as $styleId) {
162
            /** @var Style $style */
163
            $style = $this->styleRegistry->getStyleFromStyleId($styleId);
164
 
165
            $backgroundColor = $style->getBackgroundColor();
1441 ariadna 166
            $content .= \sprintf(
1 efrain 167
                '<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
168
                $backgroundColor
169
            );
170
        }
171
 
172
        $content .= '</fills>';
173
 
174
        return $content;
175
    }
176
 
177
    /**
178
     * Returns the content of the "<borders>" section.
179
     */
180
    private function getBordersSectionContent(): string
181
    {
182
        $registeredBorders = $this->styleRegistry->getRegisteredBorders();
183
 
184
        // There is one default border with index 0
185
        $borderCount = \count($registeredBorders) + 1;
186
 
187
        $content = '<borders count="'.$borderCount.'">';
188
 
189
        // Default border starting at index 0
190
        $content .= '<border><left/><right/><top/><bottom/></border>';
191
 
192
        foreach ($registeredBorders as $styleId) {
193
            $style = $this->styleRegistry->getStyleFromStyleId($styleId);
194
            $border = $style->getBorder();
195
            \assert(null !== $border);
196
            $content .= '<border>';
197
 
198
            // @see https://github.com/box/spout/issues/271
199
            foreach (BorderPart::allowedNames as $partName) {
200
                $content .= BorderHelper::serializeBorderPart($border->getPart($partName));
201
            }
202
 
203
            $content .= '</border>';
204
        }
205
 
206
        $content .= '</borders>';
207
 
208
        return $content;
209
    }
210
 
211
    /**
212
     * Returns the content of the "<cellStyleXfs>" section.
213
     */
214
    private function getCellStyleXfsSectionContent(): string
215
    {
216
        return <<<'EOD'
217
            <cellStyleXfs count="1">
218
                <xf borderId="0" fillId="0" fontId="0" numFmtId="0"/>
219
            </cellStyleXfs>
220
            EOD;
221
    }
222
 
223
    /**
224
     * Returns the content of the "<cellXfs>" section.
225
     */
226
    private function getCellXfsSectionContent(): string
227
    {
228
        $registeredStyles = $this->styleRegistry->getRegisteredStyles();
229
 
230
        $content = '<cellXfs count="'.\count($registeredStyles).'">';
231
 
232
        foreach ($registeredStyles as $style) {
233
            $styleId = $style->getId();
234
            $fillId = $this->getFillIdForStyleId($styleId);
235
            $borderId = $this->getBorderIdForStyleId($styleId);
236
            $numFmtId = $this->getFormatIdForStyleId($styleId);
237
 
238
            $content .= '<xf numFmtId="'.$numFmtId.'" fontId="'.$styleId.'" fillId="'.$fillId.'" borderId="'.$borderId.'" xfId="0"';
239
 
240
            if ($style->shouldApplyFont()) {
241
                $content .= ' applyFont="1"';
242
            }
243
 
1441 ariadna 244
            $content .= \sprintf(' applyBorder="%d"', (bool) $style->getBorder());
1 efrain 245
 
1441 ariadna 246
            if ($style->shouldApplyCellAlignment() || $style->shouldApplyCellVerticalAlignment() || $style->hasSetWrapText() || $style->shouldShrinkToFit() || $style->hasSetTextRotation()) {
1 efrain 247
                $content .= ' applyAlignment="1">';
248
                $content .= '<alignment';
249
                if ($style->shouldApplyCellAlignment()) {
1441 ariadna 250
                    $content .= \sprintf(' horizontal="%s"', $style->getCellAlignment());
1 efrain 251
                }
252
                if ($style->shouldApplyCellVerticalAlignment()) {
1441 ariadna 253
                    $content .= \sprintf(' vertical="%s"', $style->getCellVerticalAlignment());
1 efrain 254
                }
255
                if ($style->hasSetWrapText()) {
256
                    $content .= ' wrapText="'.($style->shouldWrapText() ? '1' : '0').'"';
257
                }
258
                if ($style->shouldShrinkToFit()) {
259
                    $content .= ' shrinkToFit="true"';
260
                }
1441 ariadna 261
                if ($style->hasSetTextRotation()) {
262
                    $content .= \sprintf(' textRotation="%s"', $style->textRotation());
263
                }
1 efrain 264
 
265
                $content .= '/>';
266
                $content .= '</xf>';
267
            } else {
268
                $content .= '/>';
269
            }
270
        }
271
 
272
        $content .= '</cellXfs>';
273
 
274
        return $content;
275
    }
276
 
277
    /**
278
     * Returns the content of the "<cellStyles>" section.
279
     */
280
    private function getCellStylesSectionContent(): string
281
    {
282
        return <<<'EOD'
283
            <cellStyles count="1">
284
                <cellStyle builtinId="0" name="Normal" xfId="0"/>
285
            </cellStyles>
286
            EOD;
287
    }
288
 
289
    /**
290
     * Returns the fill ID associated to the given style ID.
291
     * For the default style, we don't a fill.
292
     */
293
    private function getFillIdForStyleId(int $styleId): int
294
    {
295
        // For the default style (ID = 0), we don't want to override the fill.
296
        // Otherwise all cells of the spreadsheet will have a background color.
297
        $isDefaultStyle = (0 === $styleId);
298
 
299
        return $isDefaultStyle ? 0 : ($this->styleRegistry->getFillIdForStyleId($styleId) ?? 0);
300
    }
301
 
302
    /**
303
     * Returns the fill ID associated to the given style ID.
304
     * For the default style, we don't a border.
305
     */
306
    private function getBorderIdForStyleId(int $styleId): int
307
    {
308
        // For the default style (ID = 0), we don't want to override the border.
309
        // Otherwise all cells of the spreadsheet will have a border.
310
        $isDefaultStyle = (0 === $styleId);
311
 
312
        return $isDefaultStyle ? 0 : ($this->styleRegistry->getBorderIdForStyleId($styleId) ?? 0);
313
    }
314
 
315
    /**
316
     * Returns the format ID associated to the given style ID.
317
     * For the default style use general format.
318
     */
319
    private function getFormatIdForStyleId(int $styleId): int
320
    {
321
        // For the default style (ID = 0), we don't want to override the format.
322
        // Otherwise all cells of the spreadsheet will have a format.
323
        $isDefaultStyle = (0 === $styleId);
324
 
325
        return $isDefaultStyle ? 0 : ($this->styleRegistry->getFormatIdForStyleId($styleId) ?? 0);
326
    }
327
}