Passed
Push — master ( 924347...aff1d3 )
by Adrien
09:03 queued 11s
created

DefinedNames::getDefinedRange()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 48
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 31
c 2
b 0
f 0
nc 18
nop 1
dl 0
loc 48
ccs 29
cts 29
cp 1
crap 8
rs 8.1795
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
7
use PhpOffice\PhpSpreadsheet\DefinedName;
8
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
9
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
12
class DefinedNames
13
{
14
    private $objWriter;
15
16
    private $spreadsheet;
17
18 128
    public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet)
19
    {
20 128
        $this->objWriter = $objWriter;
21 128
        $this->spreadsheet = $spreadsheet;
22 128
    }
23
24 128
    public function write(): void
25
    {
26
        // Write defined names
27 128
        $this->objWriter->startElement('definedNames');
28
29
        // Named ranges
30 128
        if (count($this->spreadsheet->getDefinedNames()) > 0) {
31
            // Named ranges
32 16
            $this->writeNamedRangesAndFormulae();
33
        }
34
35
        // Other defined names
36 128
        $sheetCount = $this->spreadsheet->getSheetCount();
37 128
        for ($i = 0; $i < $sheetCount; ++$i) {
38
            // NamedRange for autoFilter
39 128
            $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i);
40
41
            // NamedRange for Print_Titles
42 128
            $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i);
43
44
            // NamedRange for Print_Area
45 128
            $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i);
46
        }
47
48 128
        $this->objWriter->endElement();
49 128
    }
50
51
    /**
52
     * Write defined names.
53
     */
54 16
    private function writeNamedRangesAndFormulae(): void
55
    {
56
        // Loop named ranges
57 16
        $definedNames = $this->spreadsheet->getDefinedNames();
58 16
        foreach ($definedNames as $definedName) {
59 16
            $this->writeDefinedName($definedName);
60
        }
61 16
    }
62
63
    /**
64
     * Write Defined Name for named range.
65
     */
66 16
    private function writeDefinedName(DefinedName $pDefinedName): void
67
    {
68
        // definedName for named range
69 16
        $this->objWriter->startElement('definedName');
70 16
        $this->objWriter->writeAttribute('name', $pDefinedName->getName());
71 16
        if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) {
72 1
            $this->objWriter->writeAttribute(
73 1
                'localSheetId',
74 1
                $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope())
75
            );
76
        }
77
78 16
        $definedRange = $this->getDefinedRange($pDefinedName);
79
80 16
        $this->objWriter->writeRawData($definedRange);
81
82 16
        $this->objWriter->endElement();
83 16
    }
84
85
    /**
86
     * Write Defined Name for autoFilter.
87
     */
88 128
    private function writeNamedRangeForAutofilter(Worksheet $pSheet, int $pSheetId = 0): void
89
    {
90
        // NamedRange for autoFilter
91 128
        $autoFilterRange = $pSheet->getAutoFilter()->getRange();
92 128
        if (!empty($autoFilterRange)) {
93 3
            $this->objWriter->startElement('definedName');
94 3
            $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
95 3
            $this->objWriter->writeAttribute('localSheetId', $pSheetId);
96 3
            $this->objWriter->writeAttribute('hidden', '1');
97
98
            // Create absolute coordinate and write as raw text
99 3
            $range = Coordinate::splitRange($autoFilterRange);
100 3
            $range = $range[0];
101
            //    Strip any worksheet ref so we can make the cell ref absolute
102 3
            [, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
103
104 3
            $range[0] = Coordinate::absoluteCoordinate($range[0]);
105 3
            $range[1] = Coordinate::absoluteCoordinate($range[1]);
106 3
            $range = implode(':', $range);
107
108 3
            $this->objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range);
109
110 3
            $this->objWriter->endElement();
111
        }
112 128
    }
113
114
    /**
115
     * Write Defined Name for PrintTitles.
116
     */
117 128
    private function writeNamedRangeForPrintTitles(Worksheet $pSheet, int $pSheetId = 0): void
118
    {
119
        // NamedRange for PrintTitles
120 128
        if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
121 1
            $this->objWriter->startElement('definedName');
122 1
            $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles');
123 1
            $this->objWriter->writeAttribute('localSheetId', $pSheetId);
124
125
            // Setting string
126 1
            $settingString = '';
127
128
            // Columns to repeat
129 1
            if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
130
                $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft();
131
132
                $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
133
            }
134
135
            // Rows to repeat
136 1
            if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
137 1
                if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
138
                    $settingString .= ',';
139
                }
140
141 1
                $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop();
142
143 1
                $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
144
            }
145
146 1
            $this->objWriter->writeRawData($settingString);
147
148 1
            $this->objWriter->endElement();
149
        }
150 128
    }
151
152
    /**
153
     * Write Defined Name for PrintTitles.
154
     */
155 128
    private function writeNamedRangeForPrintArea(Worksheet $pSheet, int $pSheetId = 0): void
156
    {
157
        // NamedRange for PrintArea
158 128
        if ($pSheet->getPageSetup()->isPrintAreaSet()) {
159 1
            $this->objWriter->startElement('definedName');
160 1
            $this->objWriter->writeAttribute('name', '_xlnm.Print_Area');
161 1
            $this->objWriter->writeAttribute('localSheetId', $pSheetId);
162
163
            // Print area
164 1
            $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea());
165
166 1
            $chunks = [];
167 1
            foreach ($printArea as $printAreaRect) {
168 1
                $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]);
169 1
                $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]);
170 1
                $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
171
            }
172
173 1
            $this->objWriter->writeRawData(implode(',', $chunks));
174
175 1
            $this->objWriter->endElement();
176
        }
177 128
    }
178
179 16
    private function getDefinedRange(DefinedName $pDefinedName): string
180
    {
181 16
        $definedRange = $pDefinedName->getValue();
182 16
        $splitCount = preg_match_all(
183 16
            '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
184
            $definedRange,
185
            $splitRanges,
186 16
            PREG_OFFSET_CAPTURE
187
        );
188
189 16
        $lengths = array_map('strlen', array_column($splitRanges[0], 0));
190 16
        $offsets = array_column($splitRanges[0], 1);
191
192 16
        $worksheets = $splitRanges[2];
193 16
        $columns = $splitRanges[6];
194 16
        $rows = $splitRanges[7];
195
196 16
        while ($splitCount > 0) {
197 16
            --$splitCount;
198 16
            $length = $lengths[$splitCount];
199 16
            $offset = $offsets[$splitCount];
200 16
            $worksheet = $worksheets[$splitCount][0];
201 16
            $column = $columns[$splitCount][0];
202 16
            $row = $rows[$splitCount][0];
203
204 16
            $newRange = '';
205 16
            if (empty($worksheet)) {
206 16
                if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
207
                    // We should have a worksheet
208 16
                    $worksheet = $pDefinedName->getWorksheet() ? $pDefinedName->getWorksheet()->getTitle() : null;
209
                }
210
            } else {
211 3
                $worksheet = str_replace("''", "'", trim($worksheet, "'"));
212
            }
213
214 16
            if (!empty($worksheet)) {
215 16
                $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
216
            }
217 16
            $newRange = "{$newRange}{$column}{$row}";
218
219 16
            $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
220
        }
221
222 16
        if (substr($definedRange, 0, 1) === '=') {
223 10
            $definedRange = substr($definedRange, 1);
224
        }
225
226 16
        return $definedRange;
227
    }
228
}
229