Test Failed
Push — master ( ecd78b...d05c81 )
by Kirill
02:43
created

StringValueNode::renderSpecialCharacters()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 1
nop 1
dl 0
loc 23
rs 8.9297
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL\Frontend\AST\Invocation;
11
12
use Railt\Parser\Ast\LeafInterface;
13
use Railt\Parser\Ast\Rule;
14
use Railt\SDL\Frontend\IR\Value\StringValue;
15
use Railt\SDL\Frontend\IR\Value\ValueInterface;
16
17
/**
18
 * Class StringValueNode
19
 */
20
class StringValueNode extends Rule implements AstValueInterface
21
{
22
    /**
23
     * @var string
24
     */
25
    private const UTF_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\u([0-9a-f]{4})/ui';
26
27
    /**
28
     * @var string
29
     */
30
    private const CHAR_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\(b|f|n|r|t)/u';
31
32
    /**
33
     * @return ValueInterface
34
     */
35
    public function unpack(): ValueInterface
36
    {
37
        return new StringValue($this->parse(), $this->getOffset());
38
    }
39
40
    /**
41
     * @return string
42
     */
43
    public function toPHPString(): string
44
    {
45
        return $this->parse();
46
    }
47
48
    /**
49
     * @return string
50
     */
51
    private function getValue(): string
52
    {
53
        /** @var LeafInterface $leaf */
54
        $leaf = $this->getChild(0);
55
56
        return $leaf->getValue(1);
57
    }
58
59
    /**
60
     * @return string
61
     */
62
    private function parse(): string
63
    {
64
        // "..."
65
        $result = $this->getValue();
66
67
        // Encode slashes to special "pattern" chars
68
        $result = $this->encodeSlashes($result);
69
70
        // Transform utf char \uXXXX -> X
71
        $result = $this->renderUtfSequences($result);
72
73
        // Transform special chars
74
        $result = $this->renderSpecialCharacters($result);
75
76
        // Decode special patterns to source chars (rollback)
77
        $result = $this->decodeSlashes($result);
78
79
        return $result;
80
    }
81
82
    /**
83
     * @param string $value
84
     * @return string
85
     */
86
    private function encodeSlashes(string $value): string
87
    {
88
        return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value);
89
    }
90
91
    /**
92
     * Method for parsing and decode utf-8 character
93
     * sequences like "\uXXXX" type.
94
     *
95
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
96
     * @param string $body
97
     * @return string
98
     */
99
    private function renderUtfSequences(string $body): string
100
    {
101
        $callee = function (array $matches): string {
102
            [$char, $code] = [$matches[0], $matches[1]];
0 ignored issues
show
Bug introduced by
The variable $char does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $code does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
103
104
            try {
105
                return $this->forwardRenderUtfSequences($code);
106
            } catch (\Error | \ErrorException $error) {
0 ignored issues
show
Bug introduced by
The class ErrorException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
107
                return $this->fallbackRenderUtfSequences($char);
108
            }
109
        };
110
111
        return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body;
112
    }
113
114
    /**
115
     * @param string $body
116
     * @return string
117
     */
118
    private function forwardRenderUtfSequences(string $body): string
119
    {
120
        return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE');
121
    }
122
123
    /**
124
     * @param string $body
125
     * @return string
126
     */
127
    private function fallbackRenderUtfSequences(string $body): string
128
    {
129
        try {
130
            if (\function_exists('\\json_decode')) {
131
                $result = @\json_decode('{"char": "' . $body . '"}')->char;
132
133
                if (\json_last_error() === \JSON_ERROR_NONE) {
134
                    $body = $result;
135
                }
136
            }
137
        } finally {
138
            return $body;
139
        }
140
    }
141
142
    /**
143
     * Method for parsing special control characters.
144
     *
145
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
146
     * @param string $body
147
     * @return string
148
     */
149
    private function renderSpecialCharacters(string $body): string
150
    {
151
        $callee = function (array $matches): string {
152
            [$char, $code] = [$matches[0], $matches[1]];
0 ignored issues
show
Bug introduced by
The variable $char does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $code does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
153
154
            switch ($code) {
155
                case 'b':
156
                    return "\u{0008}";
157
                case 'f':
158
                    return "\u{000C}";
159
                case 'n':
160
                    return "\u{000A}";
161
                case 'r':
162
                    return "\u{000D}";
163
                case 't':
164
                    return "\u{0009}";
165
            }
166
167
            return $char;
168
        };
169
170
        return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body;
171
    }
172
173
    /**
174
     * @param string $value
175
     * @return string
176
     */
177
    private function decodeSlashes(string $value): string
178
    {
179
        return \str_replace("\0", '\\', $value);
180
    }
181
}
182