@@ -14,6 +14,8 @@ final class ArrayParser
14
14
use ForbidCloning;
15
15
use ForbidSerialization;
16
16
17
+ private const WHITESPACE_CHARS = " \n\r\t\v\0" ;
18
+
17
19
/**
18
20
* @param string $data String representation of PostgresSQL array.
19
21
* @param \Closure(string):mixed $cast Callback to cast parsed values.
@@ -25,12 +27,10 @@ final class ArrayParser
25
27
*/
26
28
public static function parse (string $ data , \Closure $ cast , string $ delimiter = ', ' ): array
27
29
{
28
- $ data = \trim ($ data );
29
-
30
30
$ parser = new self ($ data , $ cast , $ delimiter );
31
31
$ result = $ parser ->parseToArray ();
32
32
33
- if ($ parser ->data !== '' ) {
33
+ if (isset ( $ parser ->data [ $ parser -> position ]) ) {
34
34
throw new PostgresParseException ("Data left in buffer after parsing " );
35
35
}
36
36
@@ -43,9 +43,10 @@ public static function parse(string $data, \Closure $cast, string $delimiter = '
43
43
* @param string $delimiter Delimiter used to separate values.
44
44
*/
45
45
private function __construct (
46
- private string $ data ,
46
+ private readonly string $ data ,
47
47
private readonly \Closure $ cast ,
48
- private readonly string $ delimiter = ', ' ,
48
+ private readonly string $ delimiter ,
49
+ private int $ position = 0 ,
49
50
) {
50
51
}
51
52
@@ -58,36 +59,39 @@ private function parseToArray(): array
58
59
{
59
60
$ result = [];
60
61
61
- if ($ this ->data === '' ) {
62
+ $ this ->position = $ this ->skipWhitespace ($ this ->position );
63
+
64
+ if (!isset ($ this ->data [$ this ->position ])) {
62
65
throw new PostgresParseException ("Unexpected end of data " );
63
66
}
64
67
65
- if ($ this ->data [0 ] !== '{ ' ) {
68
+ if ($ this ->data [$ this -> position ] !== '{ ' ) {
66
69
throw new PostgresParseException ("Missing opening bracket " );
67
70
}
68
71
69
- $ this ->data = \ltrim ( \substr ( $ this ->data , 1 ) );
72
+ $ this ->position = $ this -> skipWhitespace ( $ this ->position + 1 );
70
73
71
74
do {
72
- if ($ this ->data === '' ) {
75
+ if (! isset ( $ this ->data [ $ this -> position ]) ) {
73
76
throw new PostgresParseException ("Unexpected end of data " );
74
77
}
75
78
76
- if ($ this ->data [0 ] === '} ' ) { // Empty array
77
- $ this ->data = \ltrim ( \substr ( $ this ->data , 1 ) );
79
+ if ($ this ->data [$ this -> position ] === '} ' ) { // Empty array
80
+ $ this ->position = $ this -> skipWhitespace ( $ this ->position + 1 );
78
81
break ;
79
82
}
80
83
81
- if ($ this ->data [0 ] === '{ ' ) { // Array
82
- $ parser = new self ($ this ->data , $ this ->cast , $ this ->delimiter );
84
+ if ($ this ->data [$ this -> position ] === '{ ' ) { // Array
85
+ $ parser = new self ($ this ->data , $ this ->cast , $ this ->delimiter , $ this -> position );
83
86
$ result [] = $ parser ->parseToArray ();
84
- $ this ->data = $ parser ->data ;
85
- $ end = $ this ->trim ( 0 );
87
+ $ this ->position = $ parser ->position ;
88
+ $ delimiter = $ this ->moveToNextDelimiter ( $ this -> position );
86
89
continue ;
87
90
}
88
91
89
- if ($ this ->data [0 ] === '" ' ) { // Quoted value
90
- for ($ position = 1 ; isset ($ this ->data [$ position ]); ++$ position ) {
92
+ if ($ this ->data [$ this ->position ] === '" ' ) { // Quoted value
93
+ ++$ this ->position ;
94
+ for ($ position = $ this ->position ; isset ($ this ->data [$ position ]); ++$ position ) {
91
95
if ($ this ->data [$ position ] === '\\' ) {
92
96
++$ position ; // Skip next character
93
97
continue ;
@@ -102,27 +106,30 @@ private function parseToArray(): array
102
106
throw new PostgresParseException ("Could not find matching quote in quoted value " );
103
107
}
104
108
105
- $ yield = \stripslashes (\substr ($ this ->data , 1 , $ position - 1 ));
109
+ $ entry = \stripslashes (\substr ($ this ->data , $ this -> position , $ position - $ this -> position ));
106
110
107
- $ end = $ this ->trim ($ position + 1 );
111
+ $ delimiter = $ this ->moveToNextDelimiter ($ position + 1 );
108
112
} else { // Unquoted value
109
- $ position = 0 ;
110
- while (isset ($ this ->data [$ position ]) && $ this ->data [$ position ] !== $ this ->delimiter && $ this ->data [$ position ] !== '} ' ) {
113
+ $ position = $ this ->position ;
114
+ while (isset ($ this ->data [$ position ])
115
+ && $ this ->data [$ position ] !== $ this ->delimiter
116
+ && $ this ->data [$ position ] !== '} '
117
+ ) {
111
118
++$ position ;
112
119
}
113
120
114
- $ yield = \trim (\substr ($ this ->data , 0 , $ position ));
121
+ $ entry = \trim (\substr ($ this ->data , $ this -> position , $ position - $ this -> position ));
115
122
116
- $ end = $ this ->trim ($ position );
123
+ $ delimiter = $ this ->moveToNextDelimiter ($ position );
117
124
118
- if (\strcasecmp ($ yield , "NULL " ) === 0 ) { // Literal NULL is always unquoted.
125
+ if (\strcasecmp ($ entry , "NULL " ) === 0 ) { // Literal NULL is always unquoted.
119
126
$ result [] = null ;
120
127
continue ;
121
128
}
122
129
}
123
130
124
- $ result [] = ($ this ->cast )($ yield );
125
- } while ($ end !== '} ' );
131
+ $ result [] = ($ this ->cast )($ entry );
132
+ } while ($ delimiter !== '} ' );
126
133
127
134
return $ result ;
128
135
}
@@ -134,22 +141,31 @@ private function parseToArray(): array
134
141
*
135
142
* @throws PostgresParseException
136
143
*/
137
- private function trim (int $ position ): string
144
+ private function moveToNextDelimiter (int $ position ): string
138
145
{
139
- $ this -> data = \ltrim ( \substr ( $ this ->data , $ position) );
146
+ $ position = $ this ->skipWhitespace ( $ position );
140
147
141
- if ($ this ->data === '' ) {
148
+ if (! isset ( $ this ->data [ $ position ]) ) {
142
149
throw new PostgresParseException ("Unexpected end of data " );
143
150
}
144
151
145
- $ end = $ this ->data [0 ];
152
+ $ delimiter = $ this ->data [$ position ];
146
153
147
- if ($ end !== $ this ->delimiter && $ end !== '} ' ) {
154
+ if ($ delimiter !== $ this ->delimiter && $ delimiter !== '} ' ) {
148
155
throw new PostgresParseException ("Invalid delimiter " );
149
156
}
150
157
151
- $ this ->data = \ltrim (\substr ($ this ->data , 1 ));
158
+ $ this ->position = $ this ->skipWhitespace ($ position + 1 );
159
+
160
+ return $ delimiter ;
161
+ }
162
+
163
+ private function skipWhitespace (int $ position ): int
164
+ {
165
+ while (isset ($ this ->data [$ position ]) && \str_contains (self ::WHITESPACE_CHARS , $ this ->data [$ position ])) {
166
+ ++$ position ;
167
+ }
152
168
153
- return $ end ;
169
+ return $ position ;
154
170
}
155
171
}
0 commit comments