Test Failed
Push — master ( a0eed4...bb00ac )
by Kirill
149:40
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\Invocation;
11
12
use Railt\Parser\Ast\LeafInterface;
13
14
/**
15
 * Class StringValue
16
 */
17
class StringValueNode extends AbstractValueNode
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 toPHPString(): string
33
    {
34
        return $this->parse();
35
    }
36
37
    /**
38
     * @return string
39
     */
40
    private function getValue(): string
41
    {
42
        /** @var LeafInterface $leaf */
43
        $leaf = $this->getChild(0);
44
45
        return $leaf->getValue(1);
46
    }
47
48
    /**
49
     * @return string
50
     */
51
    public function parse(): string
52
    {
53
        // "..."
54
        $result = $this->getValue();
55
56
        // Encode slashes to special "pattern" chars
57
        $result = $this->encodeSlashes($result);
58
59
        // Transform utf char \uXXXX -> X
60
        $result = $this->renderUtfSequences($result);
61
62
        // Transform special chars
63
        $result = $this->renderSpecialCharacters($result);
64
65
        // Decode special patterns to source chars (rollback)
66
        $result = $this->decodeSlashes($result);
67
68
        return $result;
69
    }
70
71
    /**
72
     * @param string $value
73
     * @return string
74
     */
75
    private function encodeSlashes(string $value): string
76
    {
77
        return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value);
78
    }
79
80
    /**
81
     * Method for parsing and decode utf-8 character
82
     * sequences like "\uXXXX" type.
83
     *
84
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
85
     * @param string $body
86
     * @return string
87
     */
88
    private function renderUtfSequences(string $body): string
89
    {
90
        $callee = function (array $matches): string {
91
            [$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...
92
93
            try {
94
                return $this->forwardRenderUtfSequences($code);
95
            } 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...
96
                return $this->fallbackRenderUtfSequences($char);
97
            }
98
        };
99
100
        return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body;
101
    }
102
103
    /**
104
     * @param string $body
105
     * @return string
106
     */
107
    private function forwardRenderUtfSequences(string $body): string
108
    {
109
        return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE');
110
    }
111
112
    /**
113
     * @param string $body
114
     * @return string
115
     */
116
    private function fallbackRenderUtfSequences(string $body): string
117
    {
118
        try {
119
            if (\function_exists('\\json_decode')) {
120
                $result = @\json_decode('{"char": "' . $body . '"}')->char;
121
122
                if (\json_last_error() === \JSON_ERROR_NONE) {
123
                    $body = $result;
124
                }
125
            }
126
        } finally {
127
            return $body;
128
        }
129
    }
130
131
    /**
132
     * Method for parsing special control characters.
133
     *
134
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
135
     * @param string $body
136
     * @return string
137
     */
138
    private function renderSpecialCharacters(string $body): string
139
    {
140
        $callee = function (array $matches): string {
141
            [$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...
142
143
            switch ($code) {
144
                case 'b':
145
                    return "\u{0008}";
146
                case 'f':
147
                    return "\u{000C}";
148
                case 'n':
149
                    return "\u{000A}";
150
                case 'r':
151
                    return "\u{000D}";
152
                case 't':
153
                    return "\u{0009}";
154
            }
155
156
            return $char;
157
        };
158
159
        return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body;
160
    }
161
162
    /**
163
     * @param string $value
164
     * @return string
165
     */
166
    private function decodeSlashes(string $value): string
167
    {
168
        return \str_replace("\0", '\\', $value);
169
    }
170
}
171