-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathqjson.pas
680 lines (613 loc) · 15.7 KB
/
qjson.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
unit qjson;
interface
uses SysUtils,Classes,windows,math;
type
EJSONParserError = class(EParserError)
public
constructor Create(const str:string;i:integer;const msg:string);
end;
TQJSONType = (
QJSON_NULL,
QJSON_INT,
QJSON_FLOAT,
QJSON_BOOLEAN,
QJSON_STRING,
QJSON_ARRAY,
QJSON_MAP
);
PQJSONData = ^TQJSONData;
PQJSONArray = ^TQJSONArray;
TQJSONArray = array of PQJSONData;
TQJSONKeyValue = record
key:string;
data:PQJSONData;
end;
PQJSONMap = ^TQJSONMap;
TQJSONMap = array of TQJSONKeyValue;
TQJSONData = record
case typ:TQJSONType of
QJSON_NULL :(null:integer);
QJSON_INT :(intval:integer);
QJSON_FLOAT :(floatval:double);
QJSON_BOOLEAN:(boolval:boolean);
QJSON_STRING :(strval:PString);
QJSON_ARRAY :(arrval:PQJSONArray);
QJSON_MAP :(mapval:PQJSONMap);
end;
TQJSON = class(TObject)
private
root:PQJSONData;
procedure clear(data:PQJSONData);overload;
procedure SkipWhiteSpace(const str:string;var i:integer);
function ReadToken(const str:string;var i:integer):string;
function CreateQJSONFromString(const str:string;var i:integer):PQJSONData;
procedure SaveToFile2(data:PQJSONData;indent:integer;var fil:TextFile);
function Resolve(data:PQJSONData;keys:array of const;create:boolean=false):PQJSONData;
public
constructor Create();
constructor CreateFromFile(const filename:string);
procedure SaveToFile(const filename:string);
destructor Destroy();override;
procedure SetVal(keys:array of const;mire:integer);overload;
procedure SetValF(keys:array of const;mire:single);overload;
procedure SetVal(keys:array of const;mire:boolean);overload;
procedure SetVal(keys:array of const;mire:string);overload;
procedure Clear(keys:array of const);overload;
function GetInt(keys:array of const):integer;
function GetFloat(keys:array of const):double;
function GetString(keys:array of const):string;
function GetBool(keys:array of const):boolean;
function GetKey(keys:array of const;numkey:integer):string; //numerikusból text key-t csinál
function GetNum(keys:array of const):integer; //array length
end;
implementation
const
QJSON_NULLVAL:TQJSONData=(typ:QJSON_NULL);
constructor EJSONParserError.Create(const str:string;i:integer;const msg:string);
begin
if length(str)>i+10 then
inherited Create('JSON parse error ('+msg+') near "'+copy(str,i,30)+'"')
else
inherited Create('JSON parse error ('+msg+') near "'+copy(str,length(str)-20,30)+'"');
end;
constructor TQJSON.Create();
begin
inherited;
New(root);
root.typ:=QJSON_NULL;
end;
destructor TQJSON.Destroy;
begin
Clear(root);
Dispose(root);
inherited;
end;
procedure TQJSON.SkipWhiteSpace(const str:string;var i:integer);
begin
while (ord(str[i])<=32) and (length(str)>=i) do
inc(i);
end;
function TQJSON.ReadToken(const str:string;var i:integer):string;
var
start:integer;
begin
DecimalSeparator:='.';
SkipWhitespace(str,i);
start:=i;
result:='';
if str[i]='"' then //idézõjeles string, parse-olunk
begin
inc(i);
inc(start);
result:='';
while true do
begin
// while (str[i]<>'"') and (str[i]<>'\') and (length(str)>=i) do
while (str[i]<>'"') and (length(str)>=i) do
inc(i);
if length(str)<i then
raise EJSONParserError.Create(str,i,'unterminated string');
// if (length(str)<i+1) and (str[i]='\') then
// raise EJSONParserError.Create(str,i,'unterminated backslash');
result:=copy(str,start,i-start);
if str[i]='"' then
begin
inc(i);
result := StringReplace(result, '\b', char(8), [rfReplaceAll]);
result := StringReplace(result, '\t', char(9), [rfReplaceAll]);
result := StringReplace(result, '\n', char(10), [rfReplaceAll]);
result := StringReplace(result, '\f', char(12), [rfReplaceAll]);
result := StringReplace(result, '\r', char(13), [rfReplaceAll]);
break;
end;
end;
end
else
begin //nincs idézõjel, olvasás különleges karakterig
while (ord(str[i])>32) and (length(str)>=i) and
(str[i]<>'{') and (str[i]<>'}') and
(str[i]<>'[') and (str[i]<>']') and
(str[i]<>'(') and (str[i]<>')') and
(str[i]<>':') and (str[i]<>',') do
inc(i);
result:=copy(str,start,i-start);
end;
end;
function TQJSON.CreateQJSONFromString(const str:string;var i:integer):PQJSONData;
var
token:string;
begin
New(result);
SkipWhiteSpace(str,i);
if length(str)<i then
begin
result.typ:=QJSON_NULL;
exit;
end;
if str[i]='{' then //ojjektum
begin
result.typ:=QJSON_MAP;
New(result.mapval);
inc(i);
while true do
begin
SkipWhiteSpace(str,i);
if length(str)<i then
raise EJSONParserError.Create(str,i,'unterminated {');
if str[i]='}' then
begin
inc(i);
break;
end;
if str[i]=',' then
begin
inc(i);
continue;
end;
token:=ReadToken(str,i);
SkipWhiteSpace(str,i);
if str[i]<>':' then
raise EJSONParserError.Create(str,i,': expected');
inc(i);
setlength(result.mapval^,length(result.mapval^)+1);
result.mapval^[high(result.mapval^)].key:=token;
result.mapval^[high(result.mapval^)].data:=CreateQJSONFromString(str,i);
end;
end
else
if str[i]='[' then //tömb
begin
result.typ:=QJSON_ARRAY;
New(result.arrval);
inc(i);
while true do
begin
SkipWhiteSpace(str,i);
if length(str)<i then
raise EJSONParserError.Create(str,i,token+'unterminated [');
if str[i]=']' then
begin
inc(i);
break;
end;
if str[i]=',' then
begin
inc(i);
continue;
end;
setlength(result.arrval^,length(result.arrval^)+1);
result.arrval^[high(result.mapval^)]:=CreateQJSONFromString(str,i);
end;
end
else
if str[i]='"' then
begin
result.typ:=QJSON_STRING;
New(result.strval);
result.strval^:=ReadToken(str,i);
end
else
begin
token:=ReadToken(str,i);
if token='null' then
result.typ:=QJSON_NULL
else
if token='true' then
begin
result.typ:=QJSON_BOOLEAN;
result.boolval:=true;
end
else
if token='false' then
begin
result.typ:=QJSON_BOOLEAN;
result.boolval:=false;
end
else
begin
if TryStrToInt(token,result.intval) then //delphi 7 alatt try-olni kell
result.typ:=QJSON_INT
else
if TryStrToFloat(token,result.floatval) then
result.typ:=QJSON_FLOAT
else
raise EJSONParserError.Create(str,i,token+' is not a valid token');
end
end
end;
constructor TQJSON.CreateFromFile(const filename:string);
var
fil:File;
filehandle:Integer absolute fil; //muhhhuhuhuhahaha
str:string;
vfm:byte;
n,i,j:integer;
buf:array [0..1024] of char;
begin
inherited Create();
AssignFile(fil,Filename);
{szánalmas ez a csodásan nem thread-safe vagy OOP módja a file lockolásnak}
vfm:=FileMode;
FileMode:=fmShareDenyWrite or fmOpenRead;
Reset(fil,1);
FileMode:=vfm;
n:=GetFileSize(filehandle,nil);
SetLength(str,n);
i:=1;
while i<=n do
begin
if i+1024<=n then
BlockRead(fil,buf,1024)
else
BlockRead(fil,buf,n-i+1);
j:=0;
while (i<=n) and (j<1024) do
begin
str[i]:=buf[j];
inc(i);inc(j);
end;
end;
CloseFile(fil);
i:=1;
root:=CreateQJSONFromString(str,i);
end;
procedure TQJSON.SaveToFile2(data:PQJSONData;indent:integer;var fil:TextFile);
var
i,hgh:integer;
begin
case data.typ of
QJSON_NULL:write(fil,'null');
QJSON_INT:write(fil,data.intval);
QJSON_FLOAT:write(fil,FloatToStrF(data.floatval,ffGeneral,7,1));
QJSON_BOOLEAN: if data.boolval then write(fil,'true') else write(fil,'false');
QJSON_STRING: write(fil,'"',data.strval^,'"');
QJSON_ARRAY:
begin
hgh:=high(data.arrval^);
write(fil,'[') ;
if hgh>=3 then
writeln(fil);
for i:=0 to hgh do
begin
if hgh>=3 then
write(fil,StringOfChar(#9,indent+1));
SaveToFile2(data.arrval^[i],indent+1,fil);
if i=hgh then
write(fil)
else
write(fil,', ');
if hgh>=3 then
writeln(fil);
end;
if hgh>=3 then
write(fil,StringOfChar(#9,indent));
write(fil,']');
end;
QJSON_MAP:
begin
hgh:=high(data.mapval^);
write(fil,'{');
if hgh>=3 then
writeln(fil);
for i:=0 to hgh do
begin
if hgh>=3 then
write(fil,StringOfChar(#9,indent+1));
write(fil,'"',data.mapval^[i].key,'": ');
SaveToFile2(data.mapval^[i].data,indent+1,fil);
if i=hgh then
write(fil)
else
write(fil,', ');
if hgh>=3 then
writeln(fil);
end;
if hgh>=3 then
write(fil,StringOfChar(#9,indent));
write(fil,'}');
end;
end;
flush(fil);
end;
procedure TQJSON.SaveToFile(const filename:string);
var
fil:TextFile;
begin
Assignfile(fil,filename);
rewrite(fil);
SaveToFile2(root,0,fil);
closefile(fil);
end;
function TQJSON.Resolve(data:PQJSONData;keys:array of const;create:boolean=false):PQJSONData;
var
i,j,tmp:integer;
key:string;
begin
result:=data;
for i:=0 to high(keys) do
with keys[i] do
begin
if VType=vtInteger then
begin
if Vinteger<0 then
raise EInvalidArgument.Create('Negative index');
if (result.typ<>QJSON_ARRAY) and (result.typ<>QJSON_MAP) then //akkor csinálunk belõle
if not create then
raise EInvalidArgument.Create('Not an array')
else
begin
if result.typ=QJSON_STRING then
Dispose(result.strval);
result.typ:=QJSON_ARRAY;
New(result.arrval);
end;
if result.typ=QJSON_ARRAY then
begin
tmp:=Length(result.arrval^);
if tmp<=VInteger then
if not create then
begin
result:=@QJSON_NULLVAL; //return 0
exit;
end
else
begin
Setlength(result.arrval^,VInteger+1);
for j:=tmp to VInteger do
begin
New(result.arrval^[j]);
result.arrval^[j].typ:=QJSON_NULL;
end;
end;
result:=result.arrval^[VInteger];
end
else //ezek szerint MAP
begin
if Length(result.mapval^)<=VInteger then
if not create then
begin
result:=@QJSON_NULLVAL; //return 0
exit;
end
else
raise EInvalidArgument.Create('Overindexed map');
result:=result.mapval^[VInteger].data;
end
end
else
if (VType=vtString) or (VType=vtChar) or (VType=vtAnsiString) then
begin
if (VType=vtChar) then
key:=VChar
else
if (VType=vtAnsiString) then
key:=String(VAnsiString)
else
key:=VString^;
if key='' then
raise EInvalidArgument.Create('Zero length key string');
if result.typ=QJSON_ARRAY then
raise EInvalidArgument.Create('Array indexed with string');
if (result.typ<>QJSON_MAP) then //akkor csinálunk belõle
if not create then
raise EInvalidArgument.Create('Not a map')
else
begin
if result.typ=QJSON_STRING then
Dispose(result.strval);
result.typ:=QJSON_MAP;
New(result.mapval);
end;
tmp:=-1;
for j:=0 to high(result.mapval^) do
if result.mapval^[j].key=key then
tmp:=j;
if tmp<0 then
if not create then
begin
result:=@QJSON_NULLVAL; //return 0
exit;
end
else
begin
tmp:=length(result.mapval^);
setlength(result.mapval^,tmp+1);
result.mapval^[tmp].key:=key;
New(result.mapval^[tmp].data);
result.mapval^[tmp].data.typ:=QJSON_NULL;
end;
result:=result.mapval^[tmp].data;
end
else
raise EInvalidArgument.Create('Unknown key type');
end;
end;
procedure TQJSON.Setval(keys:array of const;mire:integer);
var
data:PQJSONData;
begin
data:=Resolve(root,keys,true);
Clear(data);
data.typ:=QJSON_INT;
data.intval:=mire;
end;
procedure TQJSON.SetvalF(keys:array of const;mire:single);
var
data:PQJSONData;
begin
data:=Resolve(root,keys,true);
Clear(data);
data.typ:=QJSON_FLOAT;
data.floatval:=mire;
end;
procedure TQJSON.Setval(keys:array of const;mire:boolean);
var
data:PQJSONData;
begin
data:=Resolve(root,keys,true);
Clear(data);
data.typ:=QJSON_BOOLEAN;
data.boolval:=mire;
end;
procedure TQJSON.Setval(keys:array of const;mire:string);
var
data:PQJSONData;
begin
data:=Resolve(root,keys,true);
if data.typ<>QJSON_STRING then
begin
Clear(data);
data.typ:=QJSON_STRING;
New(data.strval);
end;
data.strval^:=mire;
end;
procedure TQJSON.Clear(data:PQJSONData);
var
i:integer;
begin
if data.typ=QJSON_STRING then
Dispose(data.strval)
else
if data.typ=QJSON_ARRAY then
begin
for i:=0 to High(data.arrval^) do
begin
Clear(data.arrval^[i]);
Dispose(data.arrval^[i]);
end;
Dispose(data.arrval);
end
else
if data.typ=QJSON_MAP then
begin
for i:=0 to High(data.mapval^) do
begin
Clear(data.mapval^[i].data);
Dispose(data.mapval^[i].data);
end;
Dispose(data.mapval);
end;
data.typ:=QJSON_NULL;
end;
procedure TQJSON.Clear(keys:array of const);
var
data:PQJSONData;
begin
data:=Resolve(root,keys,true);
Clear(data);
end;
function TQJSON.GetInt(keys:array of const):integer;
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
result:=0;
with data^ do
case typ of
QJSON_NULL :result:=0;
QJSON_INT :result:=intval;
QJSON_FLOAT :result:=round(floatval);
QJSON_BOOLEAN:raise EConvertError.Create('Cant convert from bool to int');
QJSON_STRING :result:=StrToInt(strval^);
QJSON_ARRAY :raise EConvertError.Create('Cant convert from array to int');
QJSON_MAP :raise EConvertError.Create('Cant convert from map to int');
end;
end;
function TQJSON.GetFloat(keys:array of const):double;
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
result:=0;
with data^ do
case typ of
QJSON_NULL :result:=0;
QJSON_INT :result:=intval;
QJSON_FLOAT :result:=floatval;
QJSON_BOOLEAN:raise EConvertError.Create('Cant convert from bool to float');
QJSON_STRING :result:=StrToFloat(strval^);
QJSON_ARRAY :raise EConvertError.Create('Cant convert from array to float');
QJSON_MAP :raise EConvertError.Create('Cant convert from map to float');
end;
end;
function TQJSON.GetString(keys:array of const):string;
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
with data^ do
case typ of
QJSON_NULL :result:='';
QJSON_INT :result:=Inttostr(intval);
QJSON_FLOAT :result:=FloatToStrF(floatval,ffGeneral,12,1);
QJSON_BOOLEAN:if boolval then result:='true' else result:='false';
QJSON_STRING :result:=strval^;
QJSON_ARRAY :raise EConvertError.Create('Cant convert from array to string');
QJSON_MAP :raise EConvertError.Create('Cant convert from map to string');
end;
end;
function TQJSON.GetBool(keys:array of const):boolean;
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
result:=false;
with data^ do
case typ of
QJSON_NULL :result:=false;
QJSON_INT :result:=intval=0;
QJSON_FLOAT :result:=floatval=0;
QJSON_BOOLEAN:result:=boolval;
QJSON_STRING :result:=strval^='';
QJSON_ARRAY :raise EConvertError.Create('Cant convert from array to int');
QJSON_MAP :raise EConvertError.Create('Cant convert from map to int');
end;
end;
function TQJSON.GetKey(keys:array of const;numkey:integer):string; //numerikusból text key-t csinál
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
if data.typ<>QJSON_MAP then
raise EInvalidArgument.Create('Only maps have keys');
if numkey<0 then
raise EInvalidArgument.Create('Negative key index');
if numkey>High(data.mapval^) then
raise EInvalidArgument.Create('Overindexed map');
result:=data.mapval^[numkey].key;
end;
function TQJSON.GetNum(keys:array of const):integer;
var
data:PQJSONData;
begin
data:=Resolve(root,keys);
if data.typ=QJSON_MAP then
result:=Length(data.mapval^)
else
if data.typ=QJSON_ARRAY then
result:=Length(data.arrval^)
else
result:=0;
end;
end.