Completed
Pull Request — master (#8)
by Harry
07:25
created

State::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 6
rs 9.4285
ccs 1
cts 1
cp 1
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * This file is part of graze/csv-token
4
 *
5
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/csv-token/blob/master/LICENSE.md
11
 * @link    https://github.com/graze/csv-token
12
 */
13
14
namespace Graze\CsvToken\Tokeniser;
15
16
use Graze\CsvToken\Buffer\BufferInterface;
17
use Graze\CsvToken\Tokeniser\Token\Token;
18
use Graze\CsvToken\Tokeniser\Token\TokenStoreInterface;
19
use RuntimeException;
20
21
class State
22
{
23
    const S_ANY             = 0;
24
    const S_IN_QUOTE        = 1;
25
    const S_IN_ESCAPE       = 2;
26
    const S_IN_QUOTE_ESCAPE = 4;
27
    const S_INITIAL         = 5;
28
29
    const S_INITIAL_TOKENS         = Token::T_ANY & ~Token::T_DOUBLE_QUOTE;
30
    const S_ANY_TOKENS             = Token::T_ANY & ~Token::T_DOUBLE_QUOTE & ~Token::T_BOM;
31
    const S_IN_QUOTE_TOKENS        = Token::T_CONTENT | Token::T_QUOTE | Token::T_DOUBLE_QUOTE | Token::T_ESCAPE;
32
    const S_IN_ESCAPE_TOKENS       = Token::T_CONTENT;
33
    const S_IN_QUOTE_ESCAPE_TOKENS = Token::T_CONTENT;
34
35
    /** @var State[] */
36
    private $states;
37
    /** @var TokenStoreInterface */
38
    private $tokenStore;
39
    /** @var int */
40
    private $tokenMask;
41
    /** @var int[] */
42
    private $tokens;
43
    /** @var string[] */
44
    private $keys;
45
    /** @var int[] */
46
    private $keyLengths;
47
    /** @var int */
48
    private $maxLen;
49 29
50
    /**
51 29
     * TokenStoreInterface is passed in here, as the tokens can be modified by the store
52 29
     *
53 29
     * @param TokenStoreInterface $tokens
54
     * @param int                 $tokenMask
55
     */
56
    public function __construct(TokenStoreInterface $tokens, $tokenMask = Token::T_ANY)
57
    {
58
        $this->tokenStore = $tokens;
59
        $this->tokenMask = $tokenMask;
60 26
        $this->parseTokens();
61
    }
62 26
63 26
    /**
64 26
     * Parse the current set ok tokens and cache some metadata about them for speed
65
     */
66 22
    private function parseTokens()
67
    {
68 1
        $this->tokens = $this->tokenStore->getTokens($this->tokenMask);
69
        $this->keys = array_keys($this->tokens);
70
        $this->keyLengths = array_unique(array_map('strlen', $this->keys));
71
        arsort($this->keyLengths);
72
        $this->maxLen = reset($this->keyLengths);
73
    }
74
75 29
    /**
76
     * @param int $token
77 29
     *
78 29
     * @return State|null
79
     */
80
    public function getNextState($token)
81
    {
82
        foreach ($this->states as $mask => $state) {
83
            if ($mask & $token) {
84
                return $state;
85
            }
86 25
        }
87
88 25
        throw new RuntimeException("The supplied token: {$token} has no target state");
89 25
    }
90 24
91
    /**
92 25
     * @param int   $tokenMask
93
     * @param State $target
94 25
     */
95
    public function addStateTarget($tokenMask, State $target)
96
    {
97
        $this->states[$tokenMask] = $target;
98
    }
99
100
    /**
101
     * @param BufferInterface $buffer
102
     *
103
     * @return array
104
     */
105
    public function match(BufferInterface $buffer)
106
    {
107
        if ($this->tokenStore->hasChanged($this->tokenMask)) {
108
            $this->parseTokens();
109
        }
110
111
        $contents = $buffer->getContents();
112
        $length = $buffer->getLength();
113
        $position = $buffer->getPosition();
114
        if (count($this->tokens) > 0) {
115
            $totalLen = max($length - $this->maxLen, 1);
116
            for ($i = 0; $i < $totalLen; $i++) {
117
                foreach ($this->keyLengths as $len) {
118
                    $buf = substr($contents, $i, $len);
119
                    if (isset($this->tokens[$buf])) {
120
                        if ($i > 0) {
121
                            return [
122
                                [Token::T_CONTENT, substr($contents, 0, $i), $position, $i],
123
                                [$this->tokens[$buf], $buf, $position + $i, $len],
124
                            ];
125
                        } else {
126
                            return [[$this->tokens[$buf], $buf, $position, $len]];
127
                        }
128
                    }
129
                }
130
                if ($totalLen !== $length && $i == $totalLen - 1) {
131
                    $buffer->read();
132
                    $totalLen = $length = $buffer->getLength();
133
                    $contents = $buffer->getContents();
134
                }
135
            }
136
        }
137
138
        return [[Token::T_CONTENT, $contents[0], $buffer->getPosition(), 1]];
139
    }
140
}
141