Completed
Pull Request — master (#8)
by Harry
05:45
created

State::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
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
50
    /**
51
     * TokenStoreInterface is passed in here, as the tokens can be modified by the store
52
     *
53
     * @param TokenStoreInterface $tokens
54
     * @param int                 $tokenMask
55
     */
56 30
    public function __construct(TokenStoreInterface $tokens, $tokenMask = Token::T_ANY)
57
    {
58 30
        $this->tokenStore = $tokens;
59 30
        $this->tokenMask = $tokenMask;
60 30
        $this->parseTokens();
61 30
    }
62
63
    /**
64
     * Parse the current set ok tokens and cache some metadata about them for speed
65
     */
66 30
    private function parseTokens()
67
    {
68 30
        $this->tokens = $this->tokenStore->getTokens($this->tokenMask);
69 30
        $this->keys = array_keys($this->tokens);
70 30
        $this->keyLengths = array_unique(array_map('strlen', $this->keys));
71 30
        arsort($this->keyLengths);
72 30
        $this->maxLen = reset($this->keyLengths);
73 30
    }
74
75
    /**
76
     * @param int $token
77
     *
78
     * @return State|null
79
     */
80 27
    public function getNextState($token)
81
    {
82 27
        foreach ($this->states as $mask => $state) {
83 27
            if ($mask & $token) {
84 27
                return $state;
85
            }
86 23
        }
87
88 1
        throw new RuntimeException("The supplied token: {$token} has no target state");
89
    }
90
91
    /**
92
     * @param int   $tokenMask
93
     * @param State $target
94
     */
95 30
    public function addStateTarget($tokenMask, State $target)
96
    {
97 30
        $this->states[$tokenMask] = $target;
98 30
    }
99
100
    /**
101
     * @param BufferInterface $buffer
102
     *
103
     * @return array
104
     */
105 26
    public function match(BufferInterface $buffer)
106
    {
107 26
        if ($this->tokenStore->hasChanged($this->tokenMask)) {
108 2
            $this->parseTokens();
109 2
        }
110
111 26
        $contents = $buffer->getContents();
112 26
        $length = $buffer->getLength();
113 26
        $position = $buffer->getPosition();
114 26
        if (count($this->tokens) > 0) {
115 26
            $totalLen = max($length - $this->maxLen, 1);
116 26
            for ($i = 0; $i < $totalLen; $i++) {
117 26
                foreach ($this->keyLengths as $len) {
118 26
                    $buf = substr($contents, $i, $len);
119 26
                    if (isset($this->tokens[$buf])) {
120 25
                        if ($i > 0) {
121
                            return [
122 24
                                [Token::T_CONTENT, substr($contents, 0, $i), $position, $i],
123 24
                                [$this->tokens[$buf], $buf, $position + $i, $len],
124 24
                            ];
125
                        } else {
126 21
                            return [[$this->tokens[$buf], $buf, $position, $len]];
127
                        }
128
                    }
129 26
                }
130 26
                if ($totalLen !== $length && $i == $totalLen - 1) {
131 17
                    $buffer->read();
132 17
                    $totalLen = $length = $buffer->getLength();
133 17
                    $contents = $buffer->getContents();
134 17
                }
135 26
            }
136 7
        }
137
138 15
        return [[Token::T_CONTENT, $contents[0], $buffer->getPosition(), 1]];
139
    }
140
}
141