Completed
Push — master ( 883595...a0bcff )
by z38
02:51
created

Px::values()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 9
ccs 5
cts 6
cp 0.8333
rs 9.6666
cc 3
eloc 5
nc 3
nop 1
crap 3.0416
1
<?php
2
3
namespace Z38\PcAxis;
4
5
use RuntimeException;
6
use stdClass;
7
8
/**
9
 * PC-Axis (PX) file reader
10
 */
11
class Px
12
{
13
    const DEFAULT_CHARSET = '437';
14
15
    /**
16
     * @var string
17
     */
18
    private $path;
19
20
    /**
21
     * @var resource
22
     */
23
    private $handle;
24
25
    /**
26
     * @var array
27
     */
28
    private $keywords;
29
30
    /**
31
     * @var array
32
     */
33
    private $data;
34
35
    /**
36
     * @var int
37
     */
38
    private $dataOffset;
39
40
    /**
41
     * @var string
42
     */
43
    private $charset;
44
45
    /**
46
     * @var string
47
     */
48
    private $codepage;
49
50
    /**
51
     * Constructor
52
     *
53
     * @param string $path path to your PX file
54
     */
55 8
    public function __construct($path)
56
    {
57 8
        $this->path = $path;
58 8
        $this->charset = self::DEFAULT_CHARSET;
59 8
    }
60
61 3
    public function variables()
62
    {
63 3
        return array_merge($this->keyword('STUB')->values, $this->keyword('HEADING')->values);
64
    }
65
66
    /**
67
     * @param string $variable
68
     *
69
     * @return array
70
     */
71 3
    public function values($variable)
72
    {
73 3
        foreach ($this->keywordList('VALUES') as $keyword) {
74 3
            if ($keyword->subKeys[0] == $variable) {
75 3
                return $keyword->values;
76
            }
77 3
        }
78
        throw new RuntimeException(sprintf('Could not determine values of "%s".', $variable));
79
    }
80
81
    /**
82
     * @param string variable
83
     *
84
     * @return array|null
85
     */
86 2
    public function codes($variable)
87
    {
88 2
        foreach ($this->keywordList('CODES') as $keyword) {
89 2
            if ($keyword->subKeys[0] == $variable) {
90 1
                return $keyword->values;
91
            }
92 2
        }
93
94 1
        return null;
95
    }
96
97 2
    public function index($choices)
98
    {
99 2
        $px = $this;
100 2
        $counts = array_map(function ($variable) use ($px) {
101 2
            return count($px->values($variable));
102 2
        }, $this->variables());
103
104 2
        $index = 0;
105 2
        for ($i = 0; $i < count($choices) - 1; $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
106 2
            $index += $choices[$i] * array_product(array_slice($counts, $i + 1));
107 2
        }
108 2
        $index += end($choices);
109
110 2
        return $index;
111
    }
112
113 2
    public function datum($choices)
114
    {
115 2
        $this->assertData();
116
117 2
        $index = $this->index($choices);
118 2
        if (isset($this->data[$index])) {
119 2
            return $this->data[$index];
120
        } else {
121
            return null;
122
        }
123
    }
124
125
    public function keywords()
126
    {
127
        $this->assertKeywords();
128
129
        return $this->keywords;
130
    }
131
132 7
    public function keywordList($keyword)
133
    {
134 7
        $this->assertKeywords();
135
136 7
        if (isset($this->keywords[$keyword])) {
137 7
            return $this->keywords[$keyword];
138
        } else {
139
            return [];
140
        }
141
    }
142
143 1
    public function hasKeyword($keyword)
144
    {
145 1
        $this->assertKeywords();
146
147 1
        return isset($this->keywords[$keyword]);
148
    }
149
150 4
    public function keyword($keyword)
151
    {
152 4
        $list = $this->keywordList($keyword);
153 4
        if (!$list) {
154
            throw new RuntimeException(sprintf('Keyword "%s" does not exist.', $keyword));
155
        }
156
157 4
        return $list[0];
158
    }
159
160
    public function data()
161
    {
162
        $this->assertData();
163
164
        return $this->data;
165
    }
166
167 8
    private function parseKeywordLine($line)
168
    {
169 8
        $data = new stdClass();
170
171 8
        $line = trim(str_replace('""', ' ', $line));
172 8
        $data->raw = $line;
173
174 8
        $equalPos = self::findQuoted($line, '=');
175 8
        if ($equalPos <= 0) {
176
            return;
177
        }
178
179 8
        $key = substr($line, 0, $equalPos);
180 8
        $data->subKeys = [];
181 8
        if (substr($key, -1) == ')' && ($start = self::findQuotedReverse($key, '(')) !== false) {
182 8
            $data->subKeys = self::split(substr($key, $start + 1, -1));
183 8
            $key = substr($key, 0, $start);
184 8
        }
185 8
        $data->lang = null;
186 8
        if (substr($key, -1) == ']' && ($start = self::findQuotedReverse($key, '[')) !== false) {
187 1
            $data->lang = trim(substr($key, $start + 1, -1), '"');
188 1
            $key = substr($key, 0, $start);
189 1
        }
190
191 8
        $data->values = self::split(substr($line, $equalPos + 1));
192
193 8
        if (!isset($this->keywords[$key])) {
194 8
            $this->keywords[$key] = [];
195 8
        }
196 8
        $this->keywords[$key][] = $data;
197
198 8
        if ($key === 'CHARSET') {
199 8
            $this->charset = $data->values[0];
200 8
            if ($this->charset === 'ANSI' && $this->codepage === null) {
201 8
                $this->codepage = 'ISO-8859-1';
202 8
            }
203 8
        } elseif ($key === 'CODEPAGE') {
204 1
            $this->codepage = $data->values[0];
205 1
        }
206 8
    }
207
208 8
    private function assertKeywords()
209
    {
210 8
        if ($this->keywords !== null) {
211 4
            return;
212
        }
213
214 8
        $this->handle = fopen($this->path, 'r');
215 8
        if ($this->handle === false) {
216
            throw new RuntimeException('Could not open file.');
217
        }
218
219 8
        $this->keywords = [];
220 8
        $remainder = '';
221 8
        while (($line = fgets($this->handle)) !== false) {
222 8
            $line = trim($this->decodeLine($line));
223 8
            if ($line == 'DATA=') {
224 8
                break;
225
            }
226 8
            $remainder .= $line;
227 8
            $i = 0;
0 ignored issues
show
Unused Code introduced by
$i is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
228 8
            while (($i = self::findQuoted($remainder, ';')) !== false) {
229 8
                $this->parseKeywordLine(substr($remainder, 0, $i));
230 8
                $remainder = substr($remainder, $i + 1);
231 8
                $i = 0;
0 ignored issues
show
Unused Code introduced by
$i is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
232 8
            }
233 8
        }
234
235 8
        $this->dataOffset = ftell($this->handle);
236 8
    }
237
238 2
    private function assertData()
239
    {
240 2
        if ($this->data !== null) {
241 1
            return;
242
        }
243
244 2
        $this->assertKeywords();
245
246 2
        fseek($this->handle, $this->dataOffset, SEEK_SET);
247
248 2
        $raw = '';
249 2
        while (($line = fgets($this->handle)) !== false) {
250 2
            $line = trim($this->decodeLine($line), "\r\n");
251 2
            $raw .= $line;
252 2
        }
253
254 2
        $cells = [];
255 2
        $len = strlen($raw);
256 2
        $value = '';
257 2
        for ($i = 0;$i < $len;$i++) {
258 2
            if (!$value && $raw[$i] == '"' && ($end = strpos($raw, '"', $i + 1)) !== false) {
259 2
                $cells[] = substr($raw, $i + 1, $end - $i - 1);
260 2
                $i = $end;
261 2
                $value = '';
262 2
            } elseif (in_array($raw[$i], [' ', ',', ';', "\t"])) {
263 2
                if ($value) {
264 2
                    $cells[] = $value;
265 2
                    $value = '';
266 2
                }
267 2
            } else {
268 2
                $value .= $raw[$i];
269
            }
270 2
        }
271 2
        if ($value) {
272
            $cells[] = $value;
273
        }
274
275 2
        $this->data = $cells;
276
277 2
        fclose($this->handle);
278 2
    }
279
280 8
    private function decodeLine($line)
281
    {
282 8
        if ($this->codepage !== null) {
283 8
            $line = iconv($this->codepage, 'UTF8', $line);
284 8
        } elseif ($this->charset !== 'ANSI') {
285 8
            $line = iconv($this->charset, 'UTF8', $line);
286 8
        }
287
288 8
        return $line;
289
    }
290
291 8
    private static function split($string)
292
    {
293 8
        $values = [];
294 8
        while (($pos = self::findQuoted($string, ',')) !== false) {
295 8
            $values[] = trim(trim(substr($string, 0, $pos)), '"');
296 8
            $string = substr($string, $pos + 1);
297 8
        }
298 8
        $values[] = trim(trim($string), '"');
299
300 8
        return $values;
301
    }
302
303 8
    private static function findQuoted($haystack, $needle)
304
    {
305 8
        $pos = 0;
306 8
        while (($pos = strpos($haystack, $needle, $pos)) !== false) {
307 8
            if (substr_count($haystack, '"', 0, $pos) % 2 == 0) {
308 8
                return $pos;
309
            }
310 8
            $pos++;
311 8
        }
312
313 8
        return $pos;
314
    }
315
316 8
    private static function findQuotedReverse($haystack, $needle)
317
    {
318 8
        $len = strlen($haystack);
319 8
        $pos = strlen($haystack);
320 8
        while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false) {
321 8
            if (substr_count($haystack, '"', $pos) % 2 == 0) {
322 8
                return $pos;
323
            }
324
            $pos--;
325
        }
326
327
        return $pos;
328
    }
329
}
330