PocketMine-MP 5.18.1 git-9381fc4172e5dce4cada1cb356050c8a2ab57b94
JsonNbtParser.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20*/
21
22declare(strict_types=1);
23
24namespace pocketmine\nbt;
25
38use function is_numeric;
39use function strpos;
40use function strtolower;
41use function substr;
42use function trim;
43
45
51 public static function parseJson(string $data) : CompoundTag{
52 $stream = new BinaryStream(trim($data, " \r\n\t"));
53
54 try{
55 if(($b = $stream->get(1)) !== "{"){
56 throw new NbtDataException("Syntax error: expected compound start but got '$b'");
57 }
58 $ret = self::parseCompound($stream); //don't return directly, syntax needs to be validated
59 }catch(NbtDataException $e){
60 throw new NbtDataException($e->getMessage() . " at offset " . $stream->getOffset());
61 }catch(BinaryDataException $e){
62 throw new NbtDataException("Syntax error: " . $e->getMessage() . " at offset " . $stream->getOffset());
63 }
64 if(!$stream->feof()){
65 throw new NbtDataException("Syntax error: unexpected trailing characters after end of tag: " . $stream->getRemaining());
66 }
67
68 return $ret;
69 }
70
75 private static function parseList(BinaryStream $stream) : ListTag{
76 $retval = new ListTag();
77
78 if(self::skipWhitespace($stream, "]")){
79 while(!$stream->feof()){
80 try{
81 $value = self::readValue($stream);
82 }catch(InvalidTagValueException $e){
83 throw new NbtDataException("Data error: " . $e->getMessage());
84 }
85 $expectedType = $retval->getTagType();
86 if($expectedType !== NBT::TAG_End && $expectedType !== $value->getType()){
87 throw new NbtDataException("Data error: lists can only contain one type of value");
88 }
89 $retval->push($value);
90 if(self::readBreak($stream, "]")){
91 return $retval;
92 }
93 }
94
95 throw new NbtDataException("Syntax error: unexpected end of stream");
96 }
97
98 return $retval;
99 }
100
105 private static function parseCompound(BinaryStream $stream) : CompoundTag{
106 $retval = new CompoundTag();
107
108 if(self::skipWhitespace($stream, "}")){
109 while(!$stream->feof()){
110 $k = self::readKey($stream);
111 if($retval->getTag($k) !== null){
112 throw new NbtDataException("Syntax error: duplicate compound leaf node '$k'");
113 }
114 try{
115 $retval->setTag($k, self::readValue($stream));
116 }catch(InvalidTagValueException $e){
117 throw new NbtDataException("Data error: " . $e->getMessage());
118 }
119
120 if(self::readBreak($stream, "}")){
121 return $retval;
122 }
123 }
124
125 throw new NbtDataException("Syntax error: unexpected end of stream");
126 }
127
128 return $retval;
129 }
130
135 private static function skipWhitespace(BinaryStream $stream, string $terminator) : bool{
136 while(!$stream->feof()){
137 $b = $stream->get(1);
138 if($b === $terminator){
139 return false;
140 }
141 if($b === " " or $b === "\n" or $b === "\t" or $b === "\r"){
142 continue;
143 }
144
145 $stream->setOffset($stream->getOffset() - 1);
146 return true;
147 }
148
149 throw new NbtDataException("Syntax error: unexpected end of stream, expected start of key");
150 }
151
157 private static function readBreak(BinaryStream $stream, string $terminator) : bool{
158 if($stream->feof()){
159 throw new NbtDataException("Syntax error: unexpected end of stream, expected '$terminator'");
160 }
161 $offset = $stream->getOffset();
162 $c = $stream->get(1);
163 if($c === ","){
164 return false;
165 }
166 if($c === $terminator){
167 return true;
168 }
169
170 throw new NbtDataException("Syntax error: unexpected '$c' end at offset $offset");
171 }
172
178 private static function readValue(BinaryStream $stream) : Tag{
179 $value = "";
180 $inQuotes = false;
181
182 $offset = $stream->getOffset();
183
184 $foundEnd = false;
185
187 $retval = null;
188
189 while(!$stream->feof()){
190 $offset = $stream->getOffset();
191 $c = $stream->get(1);
192
193 if($inQuotes){ //anything is allowed inside quotes, except unescaped quotes
194 if($c === '"'){
195 $inQuotes = false;
196 $retval = new StringTag($value);
197 $foundEnd = true;
198 }elseif($c === "\\"){
199 $value .= $stream->get(1);
200 }else{
201 $value .= $c;
202 }
203 }else{
204 if($c === "," or $c === "}" or $c === "]"){ //end of parent tag
205 $stream->setOffset($stream->getOffset() - 1); //the caller needs to be able to read this character
206 $foundEnd = true;
207 break;
208 }
209
210 if($value === "" or $foundEnd){
211 if($c === "\r" or $c === "\n" or $c === "\t" or $c === " "){ //leading or trailing whitespace, ignore it
212 continue;
213 }
214
215 if($foundEnd){ //unexpected non-whitespace character after end of value
216 throw new NbtDataException("Syntax error: unexpected '$c' after end of value at offset $offset");
217 }
218 }
219
220 if($c === '"'){ //start of quoted string
221 if($value !== ""){
222 throw new NbtDataException("Syntax error: unexpected quote at offset $offset");
223 }
224 $inQuotes = true;
225
226 }elseif($c === "{"){ //start of compound tag
227 if($value !== ""){
228 throw new NbtDataException("Syntax error: unexpected compound start at offset $offset (enclose in double quotes for literal)");
229 }
230
231 $retval = self::parseCompound($stream);
232 $foundEnd = true;
233
234 }elseif($c === "["){ //start of list tag - TODO: arrays
235 if($value !== ""){
236 throw new NbtDataException("Syntax error: unexpected list start at offset $offset (enclose in double quotes for literal)");
237 }
238
239 $retval = self::parseList($stream);
240 $foundEnd = true;
241
242 }else{ //any other character
243 $value .= $c;
244 }
245 }
246 }
247
248 if($retval !== null){
249 return $retval;
250 }
251
252 if($value === ""){
253 throw new NbtDataException("Syntax error: empty value at offset $offset");
254 }
255 if(!$foundEnd){
256 throw new NbtDataException("Syntax error: unexpected end of stream at offset $offset");
257 }
258
259 $last = strtolower(substr($value, -1));
260 $part = substr($value, 0, -1);
261
262 if($last !== "b" and $last !== "s" and $last !== "l" and $last !== "f" and $last !== "d"){
263 $part = $value;
264 $last = null;
265 }
266
267 if(is_numeric($part)){
268 if($last === "f" or $last === "d" or strpos($part, ".") !== false or strpos($part, "e") !== false){ //e = scientific notation
269 $value = (float) $part;
270 switch($last){
271 case "d":
272 return new DoubleTag($value);
273 case "f":
274 default:
275 return new FloatTag($value);
276 }
277 }else{
278 $value = (int) $part;
279 switch($last){
280 case "b":
281 return new ByteTag($value);
282 case "s":
283 return new ShortTag($value);
284 case "l":
285 return new LongTag($value);
286 default:
287 return new IntTag($value);
288 }
289 }
290 }else{
291 return new StringTag($value);
292 }
293 }
294
299 private static function readKey(BinaryStream $stream) : string{
300 $key = "";
301 $offset = $stream->getOffset();
302
303 $inQuotes = false;
304 $foundEnd = false;
305
306 while(!$stream->feof()){
307 $c = $stream->get(1);
308
309 if($inQuotes){
310 if($c === '"'){
311 $inQuotes = false;
312 $foundEnd = true;
313 }elseif($c === "\\"){
314 $key .= $stream->get(1);
315 }else{
316 $key .= $c;
317 }
318 }else{
319 if($c === ":"){
320 $foundEnd = true;
321 break;
322 }
323
324 if($key === "" or $foundEnd){
325 if($c === "\r" or $c === "\n" or $c === "\t" or $c === " "){ //leading or trailing whitespace, ignore it
326 continue;
327 }
328
329 if($foundEnd){ //unexpected non-whitespace character after end of value
330 throw new NbtDataException("Syntax error: unexpected '$c' after end of value at offset $offset");
331 }
332 }
333
334 if($c === '"'){ //start of quoted string
335 if($key !== ""){
336 throw new NbtDataException("Syntax error: unexpected quote at offset $offset");
337 }
338 $inQuotes = true;
339
340 }elseif($c === "{" or $c === "}" or $c === "[" or $c === "]" or $c === ","){
341 throw new NbtDataException("Syntax error: unexpected '$c' at offset $offset (enclose in double quotes for literal)");
342 }else{ //any other character
343 $key .= $c;
344 }
345 }
346 }
347
348 if($key === ""){
349 throw new NbtDataException("Syntax error: invalid empty key at offset $offset");
350 }
351 if(!$foundEnd){
352 throw new NbtDataException("Syntax error: unexpected end of stream at offset $offset");
353 }
354
355 return $key;
356 }
357}
static parseJson(string $data)