chengkun
2025-06-05 4080b5997b38ca84b3b203c7101dcadb97b76925
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?php
 
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
 
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
 
class DataValidations
{
    private const OPERATOR_MAPPINGS = [
        'between' => DataValidation::OPERATOR_BETWEEN,
        'equal' => DataValidation::OPERATOR_EQUAL,
        'greater' => DataValidation::OPERATOR_GREATERTHAN,
        'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL,
        'less' => DataValidation::OPERATOR_LESSTHAN,
        'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL,
        'notbetween' => DataValidation::OPERATOR_NOTBETWEEN,
        'notequal' => DataValidation::OPERATOR_NOTEQUAL,
    ];
 
    private const TYPE_MAPPINGS = [
        'textlength' => DataValidation::TYPE_TEXTLENGTH,
    ];
 
    private int $thisRow = 0;
 
    private int $thisColumn = 0;
 
    private function replaceR1C1(array $matches): string
    {
        return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false);
    }
 
    public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void
    {
        $xmlX = $worksheet->children(Namespaces::URN_EXCEL);
        $sheet = $spreadsheet->getActiveSheet();
        /** @var callable $pregCallback */
        $pregCallback = [$this, 'replaceR1C1'];
        foreach ($xmlX->DataValidation as $dataValidation) {
            $cells = [];
            $validation = new DataValidation();
 
            // set defaults
            $validation->setShowDropDown(true);
            $validation->setShowInputMessage(true);
            $validation->setShowErrorMessage(true);
            $validation->setShowDropDown(true);
            $this->thisRow = 1;
            $this->thisColumn = 1;
 
            foreach ($dataValidation as $tagName => $tagValue) {
                $tagValue = (string) $tagValue;
                $tagValueLower = strtolower($tagValue);
                switch ($tagName) {
                    case 'Range':
                        foreach (explode(',', $tagValue) as $range) {
                            $cell = '';
                            if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
                                // range
                                $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
                                    . $selectionMatches[1];
                                $cell = $firstCell
                                    . ':'
                                    . Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
                                    . $selectionMatches[3];
                                $this->thisRow = (int) $selectionMatches[1];
                                $this->thisColumn = (int) $selectionMatches[2];
                                $sheet->getCell($firstCell);
                            } elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
                                // cell
                                $cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
                                    . $selectionMatches[1];
                                $sheet->getCell($cell);
                                $this->thisRow = (int) $selectionMatches[1];
                                $this->thisColumn = (int) $selectionMatches[2];
                            } elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) {
                                // column
                                $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
                                    . '1';
                                $cell = $firstCell
                                    . ':'
                                    . Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
                                    . ((string) AddressRange::MAX_ROW);
                                $this->thisColumn = (int) $selectionMatches[1];
                                $sheet->getCell($firstCell);
                            } elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) {
                                // row
                                $firstCell = 'A'
                                    . $selectionMatches[1];
                                $cell = $firstCell
                                    . ':'
                                    . AddressRange::MAX_COLUMN
                                    . $selectionMatches[1];
                                $this->thisRow = (int) $selectionMatches[1];
                                $sheet->getCell($firstCell);
                            }
 
                            $validation->setSqref($cell);
                            $stRange = $sheet->shrinkRangeToFit($cell);
                            $cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange));
                        }
 
                        break;
                    case 'Type':
                        $validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower);
 
                        break;
                    case 'Qualifier':
                        $validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower);
 
                        break;
                    case 'InputTitle':
                        $validation->setPromptTitle($tagValue);
 
                        break;
                    case 'InputMessage':
                        $validation->setPrompt($tagValue);
 
                        break;
                    case 'InputHide':
                        $validation->setShowInputMessage(false);
 
                        break;
                    case 'ErrorStyle':
                        $validation->setErrorStyle($tagValueLower);
 
                        break;
                    case 'ErrorTitle':
                        $validation->setErrorTitle($tagValue);
 
                        break;
                    case 'ErrorMessage':
                        $validation->setError($tagValue);
 
                        break;
                    case 'ErrorHide':
                        $validation->setShowErrorMessage(false);
 
                        break;
                    case 'ComboHide':
                        $validation->setShowDropDown(false);
 
                        break;
                    case 'UseBlank':
                        $validation->setAllowBlank(true);
 
                        break;
                    case 'CellRangeList':
                        // FIXME missing FIXME
 
                        break;
                    case 'Min':
                    case 'Value':
                        $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
                        $validation->setFormula1($tagValue);
 
                        break;
                    case 'Max':
                        $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
                        $validation->setFormula2($tagValue);
 
                        break;
                }
            }
 
            foreach ($cells as $cell) {
                $sheet->getCell($cell)->setDataValidation(clone $validation);
            }
        }
    }
}