1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the browscap package. |
4
|
|
|
* |
5
|
|
|
* Copyright (c) 1998-2017, Browser Capabilities Project |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
declare(strict_types = 1); |
12
|
|
|
namespace Browscap\Parser; |
13
|
|
|
|
14
|
|
|
final class IniParser implements ParserInterface |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* @var string |
18
|
|
|
*/ |
19
|
|
|
private $filename; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var bool |
23
|
|
|
*/ |
24
|
|
|
private $shouldSort = false; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private $data; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
private $fileLines; |
35
|
|
|
|
36
|
12 |
|
public function __construct(string $filename) |
37
|
|
|
{ |
38
|
12 |
|
$this->filename = $filename; |
39
|
12 |
|
} |
40
|
|
|
|
41
|
4 |
|
public function setShouldSort(bool $shouldSort) : void |
42
|
|
|
{ |
43
|
4 |
|
$this->shouldSort = $shouldSort; |
44
|
4 |
|
} |
45
|
|
|
|
46
|
3 |
|
public function shouldSort() : bool |
47
|
|
|
{ |
48
|
3 |
|
return $this->shouldSort; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @return array |
53
|
|
|
*/ |
54
|
1 |
|
public function getParsed() : array |
55
|
|
|
{ |
56
|
1 |
|
return $this->data; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @return string |
61
|
|
|
*/ |
62
|
1 |
|
public function getFilename() : string |
63
|
|
|
{ |
64
|
1 |
|
return $this->filename; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @throws \InvalidArgumentException |
69
|
|
|
* |
70
|
|
|
* @return array |
71
|
|
|
*/ |
72
|
3 |
|
public function getLinesFromFile() : array |
73
|
|
|
{ |
74
|
3 |
|
$filename = $this->filename; |
75
|
|
|
|
76
|
3 |
|
if (!file_exists($filename)) { |
77
|
1 |
|
throw new \InvalidArgumentException("File not found: {$filename}"); |
78
|
|
|
} |
79
|
|
|
|
80
|
2 |
|
return file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @param string[] $fileLines |
85
|
|
|
*/ |
86
|
4 |
|
public function setFileLines(array $fileLines) : void |
87
|
|
|
{ |
88
|
4 |
|
$this->fileLines = $fileLines; |
89
|
4 |
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @return array |
93
|
|
|
*/ |
94
|
5 |
|
public function getFileLines() : array |
95
|
|
|
{ |
96
|
5 |
|
if (!$this->fileLines) { |
|
|
|
|
97
|
1 |
|
$fileLines = $this->getLinesFromFile(); |
98
|
|
|
} else { |
99
|
4 |
|
$fileLines = $this->fileLines; |
100
|
|
|
} |
101
|
|
|
|
102
|
5 |
|
return $fileLines; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @throws \RuntimeException |
107
|
|
|
* |
108
|
|
|
* @return array |
109
|
|
|
*/ |
110
|
3 |
|
public function parse() : array |
111
|
|
|
{ |
112
|
3 |
|
$fileLines = $this->getFileLines(); |
113
|
|
|
|
114
|
3 |
|
$data = []; |
115
|
|
|
|
116
|
3 |
|
$currentSection = ''; |
117
|
3 |
|
$currentDivision = ''; |
118
|
|
|
|
119
|
3 |
|
for ($line = 0, $count = count($fileLines); $line < $count; ++$line) { |
120
|
3 |
|
$currentLine = ($fileLines[$line]); |
121
|
3 |
|
$currentLineLength = mb_strlen($currentLine); |
122
|
|
|
|
123
|
3 |
|
if (0 === $currentLineLength) { |
124
|
2 |
|
continue; |
125
|
|
|
} |
126
|
|
|
|
127
|
3 |
|
if (';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' === mb_substr($currentLine, 0, 40)) { |
128
|
2 |
|
$currentDivision = trim(mb_substr($currentLine, 41)); |
129
|
|
|
|
130
|
2 |
|
continue; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// We only skip comments that *start* with semicolon |
134
|
3 |
|
if (';' === $currentLine[0]) { |
135
|
2 |
|
continue; |
136
|
|
|
} |
137
|
|
|
|
138
|
3 |
|
if ('[' === $currentLine[0]) { |
139
|
2 |
|
$currentSection = mb_substr($currentLine, 1, ($currentLineLength - 2)); |
140
|
|
|
|
141
|
2 |
|
continue; |
142
|
|
|
} |
143
|
|
|
|
144
|
3 |
|
$bits = explode('=', $currentLine); |
145
|
|
|
|
146
|
3 |
|
if (2 < count($bits)) { |
147
|
1 |
|
throw new \RuntimeException("Too many equals in line: {$currentLine}, in Division: {$currentDivision}"); |
148
|
|
|
} |
149
|
|
|
|
150
|
2 |
|
if (2 > count($bits)) { |
151
|
1 |
|
$bits[1] = ''; |
152
|
|
|
} |
153
|
|
|
|
154
|
2 |
|
$data[$currentSection][$bits[0]] = $bits[1]; |
155
|
2 |
|
$data[$currentSection]['Division'] = $currentDivision; |
156
|
|
|
} |
157
|
|
|
|
158
|
2 |
|
if ($this->shouldSort()) { |
159
|
2 |
|
$data = $this->sortArrayAndChildArrays($data); |
160
|
|
|
} |
161
|
|
|
|
162
|
2 |
|
$this->data = $data; |
163
|
|
|
|
164
|
2 |
|
return $data; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param array $array |
169
|
|
|
* |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
4 |
|
private function sortArrayAndChildArrays(array $array) : array |
173
|
|
|
{ |
174
|
4 |
|
ksort($array); |
175
|
|
|
|
176
|
4 |
|
foreach (array_keys($array) as $key) { |
177
|
4 |
|
if (!is_array($array[$key]) || empty($array[$key])) { |
178
|
4 |
|
continue; |
179
|
|
|
} |
180
|
|
|
|
181
|
3 |
|
$array[$key] = $this->sortArrayAndChildArrays($array[$key]); |
182
|
|
|
} |
183
|
|
|
|
184
|
4 |
|
return $array; |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.