Test Failed
Push — master ( 6bac61...6350a6 )
by Kirill
03:02
created

StringValueNode::parse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 19
rs 9.6333
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\Value;
11
12
use Railt\Parser\Ast\Rule;
13
14
/**
15
 * Class StringValueNode
16
 */
17
class StringValueNode extends Rule implements ValueInterface
18
{
19
    /**
20
     * @var string
21
     */
22
    private const UTF_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\u([0-9a-f]{4})/ui';
23
24
    /**
25
     * @var string
26
     */
27
    private const CHAR_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\(b|f|n|r|t)/u';
28
29
    /**
30
     * @return string
31
     */
32
    public function toString(): string
33
    {
34
        $result = \addcslashes($this->toPrimitive(), '"\\');
35
36
        $result = \str_replace(
37
            ["\b", "\f", "\n", "\r", "\t"],
38
            ['\u0092', '\u0012', '\u0010', '\u0013', '\u0009'],
39
            $result
40
        );
41
42
        return \sprintf('"%s"', $result);
43
    }
44
45
    /**
46
     * @return string
47
     */
48
    public function toPrimitive(): string
49
    {
50
        return $this->parse();
51
    }
52
53
    /**
54
     * @return string
55
     */
56
    private function getValue(): string
57
    {
58
        return $this->getChild(0)->getValue(1);
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on Railt\Parser\Ast\NodeInterface. Did you maybe mean getValues()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
59
    }
60
61
    /**
62
     * @return string
63
     */
64
    private function parse(): string
65
    {
66
        // "..."
67
        $result = $this->getValue();
68
69
        // Encode slashes to special "pattern" chars
70
        $result = $this->encodeSlashes($result);
71
72
        // Transform utf char \uXXXX -> X
73
        $result = $this->renderUtfSequences($result);
74
75
        // Transform special chars
76
        $result = $this->renderSpecialCharacters($result);
77
78
        // Decode special patterns to source chars (rollback)
79
        $result = $this->decodeSlashes($result);
80
81
        return $result;
82
    }
83
84
    /**
85
     * @param string $value
86
     * @return string
87
     */
88
    private function encodeSlashes(string $value): string
89
    {
90
        return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value);
91
    }
92
93
    /**
94
     * Method for parsing and decode utf-8 character
95
     * sequences like "\uXXXX" type.
96
     *
97
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
98
     * @param string $body
99
     * @return string
100
     */
101
    private function renderUtfSequences(string $body): string
102
    {
103
        $callee = function (array $matches): string {
104
            [$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...
105
106
            try {
107
                return $this->forwardRenderUtfSequences($code);
108
            } 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...
109
                return $this->fallbackRenderUtfSequences($char);
110
            }
111
        };
112
113
        return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body;
114
    }
115
116
    /**
117
     * @param string $body
118
     * @return string
119
     */
120
    private function forwardRenderUtfSequences(string $body): string
121
    {
122
        return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE');
123
    }
124
125
    /**
126
     * @param string $body
127
     * @return string
128
     */
129
    private function fallbackRenderUtfSequences(string $body): string
130
    {
131
        try {
132
            if (\function_exists('\\json_decode')) {
133
                $result = @\json_decode('{"char": "' . $body . '"}')->char;
134
135
                if (\json_last_error() === \JSON_ERROR_NONE) {
136
                    $body = $result;
137
                }
138
            }
139
        } finally {
140
            return $body;
141
        }
142
    }
143
144
    /**
145
     * Method for parsing special control characters.
146
     *
147
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
148
     * @param string $body
149
     * @return string
150
     */
151
    private function renderSpecialCharacters(string $body): string
152
    {
153
        $callee = function (array $matches): string {
154
            [$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...
155
156
            switch ($code) {
157
                case 'b':
158
                    return "\u{0008}";
159
                case 'f':
160
                    return "\u{000C}";
161
                case 'n':
162
                    return "\u{000A}";
163
                case 'r':
164
                    return "\u{000D}";
165
                case 't':
166
                    return "\u{0009}";
167
            }
168
169
            return $char;
170
        };
171
172
        return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body;
173
    }
174
175
    /**
176
     * @param string $value
177
     * @return string
178
     */
179
    private function decodeSlashes(string $value): string
180
    {
181
        return \str_replace("\0", '\\', $value);
182
    }
183
}
184