Splitter   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 66
dl 0
loc 156
rs 10
c 2
b 0
f 0
wmc 22

5 Methods

Rating   Name   Duplication   Size   Complexity  
A parseTags() 0 19 4
A __construct() 0 8 2
B split() 0 41 7
A parseMoves() 0 13 2
B parseLimbo() 0 16 7
1
<?php declare(strict_types=1);
2
/**
3
 * pgn-splitter (https://github.com/chesszebra/pgn-splitter)
4
 *
5
 * @link https://github.com/chesszebra/pgn-splitter for the canonical source repository
6
 * @copyright Copyright (c) 2017 Chess Zebra (https://chesszebra.com)
7
 * @license https://github.com/chesszebra/pgn-splitter/blob/master/LICENSE MIT
8
 */
9
10
namespace ChessZebra\Chess\Pgn;
11
12
use ChessZebra\Chess\Pgn\Exception\InvalidStreamException;
13
14
final class Splitter
15
{
16
    const SPLIT_GAMES = 0;
17
    const SPLIT_CHUNKS = 1;
18
19
    const STATE_LIMBO = 0;
20
    const STATE_TAGS = 1;
21
    const STATE_MOVES = 2;
22
23
    /**
24
     * The stream to split.
25
     *
26
     * @var resource
27
     */
28
    private $stream;
29
30
    /**
31
     * The mode to split the stream on.
32
     *
33
     * @var int
34
     */
35
    private $mode;
36
37
    /**
38
     * The current state of the splitter.
39
     *
40
     * @var int
41
     */
42
    private $state;
43
44
    /**
45
     * The parsed buffer.
46
     *
47
     * @var string
48
     */
49
    private $buffer;
50
51
    /**
52
     * Initializes a new instance of this class.
53
     *
54
     * @param resource $stream The stream to split.
55
     * @param int $mode The mode to split on.
56
     * @throws InvalidStreamException Thrown when an invalid stream is provided.
57
     */
58
    public function __construct($stream, int $mode = self::SPLIT_GAMES)
59
    {
60
        if (!is_resource($stream)) {
61
            throw new InvalidStreamException('The provided stream is not a valid resource.');
62
        }
63
64
        $this->stream = $stream;
65
        $this->mode = $mode;
66
    }
67
68
    /**
69
     * Splits the stream into loose chunks and provides them to the given callback.
70
     *
71
     * @param callable $callback The callback that is called for each found chunk.
72
     * @return int Returns the amount of chunks found in the stream.
73
     */
74
    public function split(callable $callback): int
75
    {
76
        $result = 0;
77
78
        $this->buffer = '';
79
        $this->state = self::STATE_LIMBO;
80
81
        while (!feof($this->stream)) {
82
            $line = fgets($this->stream);
83
            $line = str_replace("\r\n", "\n", $line); // Replace Windows line endings with unix
84
            $line = str_replace("\r", "\n", $line); // Replace mac line endings with unix
85
86
            if (!$line) {
87
                continue;
88
            }
89
90
            switch ($this->state) {
91
                case self::STATE_LIMBO:
92
                    $this->parseLimbo($line);
93
                    break;
94
95
                case self::STATE_TAGS:
96
                    $this->parseTags($callback, $result, $line);
97
                    break;
98
99
                case self::STATE_MOVES:
100
                    $this->parseMoves($callback, $result, $line);
101
                    break;
102
103
                default:
104
                    throw new \RuntimeException(sprintf('The state "%d" is not implemented.', $this->state));
105
            }
106
        }
107
108
        if (trim($this->buffer) !== '') {
109
            $result++;
110
            $callback($this->buffer);
111
            $this->buffer = '';
112
        }
113
114
        return $result;
115
    }
116
117
    private function parseLimbo(string $line): void
118
    {
119
        if (trim($line) === '') {
120
            return;
121
        }
122
123
        if (strlen($line) >= 3 && $line[0] === "\xEF" && $line[1] === "\xBB" && $line[2] === "\xBF") {
124
            $line = substr($line, 3);
125
        }
126
127
        if ($line[0] === '[') {
128
            $this->buffer .= $line;
129
            $this->state = self::STATE_TAGS;
130
        } else {
131
            $this->buffer .= $line;
132
            $this->state = self::STATE_MOVES;
133
        }
134
    }
135
136
    private function parseTags(callable $callback, int &$result, string $line): void
137
    {
138
        if ($line[0] === '[') {
139
            $this->buffer .= $line;
140
            return;
141
        }
142
143
        if ($this->mode === self::SPLIT_CHUNKS && trim($this->buffer) !== '') {
144
            $result++;
145
146
            $callback($this->buffer);
147
148
            $this->buffer = '';
149
            return;
150
        }
151
152
        $this->buffer .= $line;
153
154
        $this->state = self::STATE_MOVES;
155
    }
156
157
    private function parseMoves(callable $callback, int &$result, string $line): void
158
    {
159
        if ($line === "\n") {
160
            $this->state = self::STATE_LIMBO;
161
162
            $result++;
163
164
            $callback($this->buffer);
165
166
            $this->buffer = '';
167
        }
168
169
        $this->buffer .= $line;
170
    }
171
}
172