State::match()   D
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 35
ccs 21
cts 21
cp 1
rs 4.909
cc 9
eloc 23
nc 8
nop 1
crap 9
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 31
    public function __construct(TokenStoreInterface $tokens, $tokenMask = Token::T_ANY)
57
    {
58 31
        $this->tokenStore = $tokens;
59 31
        $this->tokenMask = $tokenMask;
60 31
        $this->parseTokens();
61 31
    }
62
63
    /**
64
     * Parse the current set ok tokens and cache some metadata about them for speed
65
     */
66 31
    private function parseTokens()
67
    {
68 31
        $this->tokens = $this->tokenStore->getTokens($this->tokenMask);
69 31
        $this->keys = array_keys($this->tokens);
70 31
        $this->keyLengths = array_unique(array_map('strlen', $this->keys));
71 31
        arsort($this->keyLengths);
72 31
        $this->maxLen = reset($this->keyLengths);
73 31
    }
74
75
    /**
76
     * @param int $token
77
     *
78
     * @return State|null
79
     */
80 28
    public function getNextState($token)
81
    {
82 28
        foreach ($this->states as $mask => $state) {
83 28
            if ($mask & $token) {
84 28
                return $state;
85
            }
86
        }
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 31
    public function addStateTarget($tokenMask, State $target)
96
    {
97 31
        $this->states[$tokenMask] = $target;
98 31
    }
99
100
    /**
101
     * @param BufferInterface $buffer
102
     *
103
     * @return array
104
     */
105 27
    public function match(BufferInterface $buffer)
106
    {
107 27
        if ($this->tokenStore->hasChanged($this->tokenMask)) {
108 2
            $this->parseTokens();
109
        }
110
111 27
        $contents = $buffer->getContents();
112 27
        $length = $buffer->getLength();
113 27
        $position = $buffer->getPosition();
114 27
        if (count($this->tokens) > 0) {
115 27
            $totalLen = max($length - $this->maxLen, 1);
116 27
            for ($i = 0; $i < $totalLen; $i++) {
117 27
                foreach ($this->keyLengths as $len) {
118 27
                    $buf = substr($contents, $i, $len);
119 27
                    if (isset($this->tokens[$buf])) {
120 26
                        if ($i > 0) {
121
                            return [
122 25
                                [Token::T_CONTENT, substr($contents, 0, $i), $position, $i],
123 25
                                [$this->tokens[$buf], $buf, $position + $i, $len],
124
                            ];
125
                        } else {
126 27
                            return [[$this->tokens[$buf], $buf, $position, $len]];
127
                        }
128
                    }
129
                }
130 27
                if ($totalLen !== $length && $i == $totalLen - 1) {
131 17
                    $buffer->read();
132 17
                    $totalLen = $length = $buffer->getLength();
133 17
                    $contents = $buffer->getContents();
134
                }
135
            }
136
        }
137
138 16
        return [[Token::T_CONTENT, $contents[0], $buffer->getPosition(), 1]];
139
    }
140
}
141