PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.0
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
bit-form / vendor / arif-un / jcof / src / Jcof.php

Jcof.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.1.0, at vendor/arif-un/jcof/src/Jcof.php

702 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\Jcof;
4
5 class Jcof
6 {
7 private const B62_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
8
9 private const B62_ALPHABET_NUM = [
10 '0' => 0,
11 '1' => 1,
12 '2' => 2,
13 '3' => 3,
14 '4' => 4,
15 '5' => 5,
16 '6' => 6,
17 '7' => 7,
18 '8' => 8,
19 '9' => 9,
20 'a' => 10,
21 'b' => 11,
22 'c' => 12,
23 'd' => 13,
24 'e' => 14,
25 'f' => 15,
26 'g' => 16,
27 'h' => 17,
28 'i' => 18,
29 'j' => 19,
30 'k' => 20,
31 'l' => 21,
32 'm' => 22,
33 'n' => 23,
34 'o' => 24,
35 'p' => 25,
36 'q' => 26,
37 'r' => 27,
38 's' => 28,
39 't' => 29,
40 'u' => 30,
41 'v' => 31,
42 'w' => 32,
43 'x' => 33,
44 'y' => 34,
45 'z' => 35,
46 'A' => 36,
47 'B' => 37,
48 'C' => 38,
49 'D' => 39,
50 'E' => 40,
51 'F' => 41,
52 'G' => 42,
53 'H' => 43,
54 'I' => 44,
55 'J' => 45,
56 'K' => 46,
57 'L' => 47,
58 'M' => 48,
59 'N' => 49,
60 'O' => 50,
61 'P' => 51,
62 'Q' => 52,
63 'R' => 53,
64 'S' => 54,
65 'T' => 55,
66 'U' => 56,
67 'V' => 57,
68 'W' => 58,
69 'X' => 59,
70 'Y' => 60,
71 'Z' => 61,
72 ];
73
74 public static function parse($str)
75 {
76 $self = new self();
77 $r = new StringReader($str);
78 $stringTable = $self->parseStringTable($r);
79 $r->skip(';');
80 $objectShapeTable = $self->parseObjectShapeTable($r, $stringTable);
81 $r->skip(';');
82
83 return $self->parseValue($r, $stringTable, $objectShapeTable);
84 }
85
86 public static function stringify($value)
87 {
88 $self = new self();
89 $w = new StringWriter();
90 $meta = $self->analyze($value);
91
92 $self->stringifyStringTable($w, $meta);
93 $w->write(';');
94 $self->stringifyObjectShapeTable($w, $meta);
95 $w->write(';');
96 $self->stringifyValue($w, $meta, $value);
97
98 return $w->str;
99 }
100
101 public function arrayIsList(array $arr)
102 {
103 if ($arr === []) {
104 return true;
105 }
106
107 return array_keys($arr) === range(0, count($arr) - 1);
108 }
109
110 public function analyzeValue($value, &$strings, &$objectShapes)
111 {
112 if (is_array($value) && $this->arrayIsList($value)) {
113 foreach ($value as $v) {
114 $this->analyzeValue($v, $strings, $objectShapes);
115 }
116 } elseif (is_array($value) && ! $this->arrayIsList($value)) {
117 $keys = array_keys($value);
118 sort($keys);
119
120 if (count($keys) > 1) {
121 $shapeHash = json_encode($keys);
122 if (! isset($objectShapes[$shapeHash])) {
123 $objectShapes[$shapeHash] = ['count' => 1, 'keys' => $keys];
124 } else {
125 $objectShapes[$shapeHash]['count'] += 1;
126 }
127 } elseif (count($keys) == 1) {
128 if (! isset($strings[$keys[0]])) {
129 $strings[$keys[0]] = ['count' => 1];
130 } else {
131 $strings[$keys[0]]['count'] += 1;
132 }
133 }
134
135 foreach ($keys as $key) {
136 $this->analyzeValue($value[$key], $strings, $objectShapes);
137 }
138 } elseif (is_string($value) && strlen($value) > 1) {
139 if (! isset($strings[$value])) {
140 $strings[$value] = ['count' => 1];
141 } else {
142 $strings[$value]['count'] += 1;
143 }
144 }
145 }
146
147 public function analyze($value)
148 {
149 $strings = [];
150 $objectShapes = [];
151 $this->analyzeValue($value, $strings, $objectShapes);
152
153 foreach ($objectShapes as $hash => $shape) {
154 if ($shape['count'] == 1) {
155 unset($objectShapes[$hash]);
156 }
157
158 foreach ($shape['keys'] as $key) {
159 if (! isset($strings[$key])) {
160 $strings[$key] = ['count' => 1];
161 } else {
162 $strings[$key]['count'] += 1;
163 }
164 }
165 }
166
167 foreach ($strings as $string => $s) {
168 if ($s['count'] == 1) {
169 unset($strings[$string]);
170 }
171 }
172
173 $stringList = array_keys($strings);
174 usort($stringList, function ($a, $b) use ($strings) {
175 return $strings[$b]['count'] - $strings[$a]['count'];
176 });
177
178 $stringIds = [];
179 for ($id = 0; $id < count($stringList); $id++) {
180 $stringIds[$stringList[$id]] = $id;
181 }
182
183 $objectShapeList = [];
184 $objectShapeIds = [];
185 foreach ($objectShapes as $hash => $shape) {
186 $objectShapeIds[$hash] = count($objectShapeList);
187 $objectShapeList[] = $shape['keys'];
188 }
189
190 return [
191 'stringList' => $stringList,
192 'stringIds' => $stringIds,
193 'objectShapeList' => $objectShapeList,
194 'objectShapeIds' => $objectShapeIds,
195 ];
196 }
197
198 public function stringifyStringTable($w, $meta)
199 {
200 if (count($meta['stringList']) == 0) {
201 return;
202 }
203
204 $this->stringifyString($w, $meta['stringList'][0]);
205 for ($i = 1; $i < count($meta['stringList']); $i++) {
206 $w->maybeSep(',');
207 $this->stringifyString($w, $meta['stringList'][$i]);
208 }
209 }
210
211 public function stringifyString($w, $string)
212 {
213 if (preg_match('/^[a-zA-Z0-9]+$/', $string)) {
214 $w->write($string);
215 } else {
216 $w->write(json_encode($string));
217 }
218 }
219
220 public function stringifyObjectShapeTable($w, $meta)
221 {
222 if (count($meta['objectShapeList']) == 0) {
223 return;
224 }
225
226 $this->stringifyObjectShape($w, $meta, $meta['objectShapeList'][0]);
227 for ($i = 1; $i < count($meta['objectShapeList']); $i++) {
228 $w->write(',');
229 $this->stringifyObjectShape($w, $meta, $meta['objectShapeList'][$i]);
230 }
231 }
232
233 public function stringifyObjectShape($w, $meta, $shape)
234 {
235 $this->stringifyObjectKey($w, $meta, $shape[0]);
236 for ($i = 1; $i < count($shape); $i++) {
237 $w->maybeSep(':');
238 $this->stringifyObjectKey($w, $meta, $shape[$i]);
239 }
240 }
241
242 public function stringifyObjectKey($w, $meta, $key)
243 {
244 $id = isset($meta['stringIds'][$key]) ? $meta['stringIds'][$key] : null;
245 if ($id === null) {
246 $w->write(json_encode($key));
247 } else {
248 $this->stringifyBase62($w, $id);
249 }
250 }
251
252 public function stringifyBase62($w, $num)
253 {
254 $str = '';
255 do {
256 $str .= self::B62_ALPHABET[$num % 62];
257 $num = intval($num / 62);
258 } while ($num > 0);
259
260 for ($i = strlen($str) - 1; $i >= 0; $i--) {
261 $w->write(substr($str, $i, 1));
262 }
263 }
264
265 public function stringifyValue($w, $meta, $value)
266 {
267 if (is_array($value) && $this->arrayIsList($value)) {
268 $w->write('[');
269 if (count($value) == 0) {
270 $w->write(']');
271
272 return;
273 }
274 // print_r($value);
275 $this->stringifyValue($w, $meta, $value[0]);
276 for ($i = 1; $i < count($value); $i++) {
277 $w->maybeSep(',');
278 $this->stringifyValue($w, $meta, $value[$i]);
279 }
280
281 $w->write(']');
282 } elseif (is_array($value) && ! $this->arrayIsList($value)) {
283 $keys = array_keys($value);
284 sort($keys);
285 $hash = json_encode($keys);
286 $shapeId = isset($meta['objectShapeIds'][$hash]) ? $meta['objectShapeIds'][$hash] : null;
287 if ($shapeId === null) {
288 $this->stringifyKeyedObjectValue($w, $meta, $value, $keys);
289 } else {
290 $this->stringifyShapedObjectValue($w, $meta, $value, $keys, $shapeId);
291 }
292 } elseif (is_numeric($value) && ! is_string($value)) {
293 if ($value == intval($value) && ($value < 0 || $value > 10)) {
294 $w->write($value < 0 ? 'I' : 'i');
295 $this->stringifyBase62($w, abs($value));
296 } elseif (is_infinite($value) || is_nan($value)) {
297 $w->write('n');
298 } else {
299 $w->write(json_encode($value));
300 }
301 } elseif (is_string($value)) {
302 $stringId = isset($meta['stringIds'][$value]) ? $meta['stringIds'][$value] : null;
303 if ($stringId === null) {
304 $w->write(json_encode($value));
305 } else {
306 $w->write('s');
307 $this->stringifyBase62($w, $stringId);
308 }
309 } elseif (is_bool($value)) {
310 $w->write($value ? 'b' : 'B');
311 } elseif ($value === null) {
312 $w->write('n');
313 } else {
314 throw new Exception("Can't serialize value: ".$value);
315 }
316 }
317
318 public function stringifyShapedObjectValue($w, $meta, $value, $keys, $shapeId)
319 {
320 $w->write('(');
321 $this->stringifyBase62($w, $shapeId);
322 if (count($keys) == 0) {
323 $w->write(')');
324
325 return;
326 }
327
328 foreach ($keys as $key) {
329 $w->maybeSep(',');
330 $this->stringifyValue($w, $meta, $value[$key]);
331 }
332
333 $w->write(')');
334 }
335
336 public function stringifyKeyedObjectValue($w, $meta, $value, $keys)
337 {
338 $w->write('{');
339 if (count($keys) == 0) {
340 $w->write('}');
341
342 return;
343 }
344
345 $this->stringifyKeyValuePair($w, $meta, $keys[0], $value[$keys[0]]);
346 for ($i = 1; $i < count($keys); $i++) {
347 $w->maybeSep(',');
348 $this->stringifyKeyValuePair($w, $meta, $keys[$i], $value[$keys[$i]]);
349 }
350
351 $w->write('}');
352 }
353
354 public function stringifyKeyValuePair($w, $meta, $key, $val)
355 {
356 $this->stringifyObjectKey($w, $meta, $key);
357 $w->maybeSep(':');
358 $this->stringifyValue($w, $meta, $val);
359 }
360
361 public function parseStringTable($r)
362 {
363 if ($r->peek() == ';') {
364 return [];
365 }
366
367 $strings = [];
368 while (true) {
369 $strings[] = $this->parseString($r);
370 $ch = $r->peek();
371 if ($ch == ';') {
372 return $strings;
373 } elseif ($ch == ',') {
374 $r->consume();
375 }
376 }
377 }
378
379 public function parseString($r)
380 {
381 if ($r->peek() == '"') {
382 return $this->parseJsonString($r);
383 } elseif (preg_match('/[a-zA-Z0-9]/', $r->peek())) {
384 return $this->parsePlainString($r);
385 } else {
386 $r->error('Expected plain string or JSON string');
387 }
388 }
389
390 public function parsePlainString($r)
391 {
392 $str = $r->peek();
393 $r->consume();
394
395 while (true) {
396 $ch = $r->peek();
397 if (! preg_match('/[a-zA-Z0-9]/', $ch)) {
398 return $str;
399 }
400
401 $str .= $ch;
402 $r->consume();
403 }
404 }
405
406 public function parseJsonString($r)
407 {
408 $start = $r->index;
409 $r->skip('"');
410 while (true) {
411 $ch = $r->peek();
412 $r->consume();
413 if ($ch == '"') {
414 break;
415 } elseif ($ch == '\\') {
416 $r->consume();
417 } elseif ($ch === null) {
418 $r->error('Unexpected EOF');
419 }
420 }
421
422 $substring = substr($r->str, $start, $r->index - $start);
423
424 return json_decode($substring, true);
425 }
426
427 public function parseObjectShapeTable($r, $stringTable)
428 {
429 if ($r->peek() == ';') {
430 return [];
431 }
432
433 $shapes = [];
434 while (true) {
435 $shapes[] = $this->parseObjectShape($r, $stringTable);
436 $ch = $r->peek();
437 if ($ch == ';') {
438 return $shapes;
439 } elseif ($ch == ',') {
440 $r->consume();
441 }
442 }
443 }
444
445 public function parseObjectShape($r, $stringTable)
446 {
447 $shape = [];
448 while (true) {
449 $shape[] = $this->parseObjectKey($r, $stringTable);
450 $ch = $r->peek();
451 if ($ch == ',' || $ch == ';') {
452 return $shape;
453 } elseif ($ch == ':') {
454 $r->consume();
455 }
456 }
457 }
458
459 public function parseObjectKey($r, $stringTable)
460 {
461 if ($r->peek() == '"') {
462 return $this->parseJsonString($r);
463 } else {
464 $id = $this->parseBase62($r);
465 if ($id >= count($stringTable)) {
466 $r->error('String ID '.$id.' out of range');
467 }
468
469 return $stringTable[$id];
470 }
471 }
472
473 public function parseBase62($r)
474 {
475 if (! preg_match('/[0-9a-zA-Z]/', $r->peek())) {
476 $r->error('Expected base62 value');
477 }
478
479 $num = 0;
480 while (true) {
481 $num *= 62;
482 $num += self::B62_ALPHABET_NUM[$r->peek()];
483 $r->consume();
484 if (! preg_match('/[0-9a-zA-Z]/', $r->peek())) {
485 return $num;
486 }
487 }
488 }
489
490 public function parseValue($r, $stringTable, $objectShapeTable)
491 {
492 $ch = $r->peek();
493 if ($ch == '[') {
494 return $this->parseArrayValue($r, $stringTable, $objectShapeTable);
495 } elseif ($ch == '(') {
496 return $this->parseShapedObjectValue($r, $stringTable, $objectShapeTable);
497 } elseif ($ch == '{') {
498 return $this->parseKeyedObjectValue($r, $stringTable, $objectShapeTable);
499 } elseif (preg_match('/[iIf0-9\-]/', $ch)) {
500 return $this->parseNumberValue($r);
501 } elseif ($ch == 's' || $ch == '"') {
502 return $this->parseStringValue($r, $stringTable);
503 } elseif ($ch == 'b') {
504 $r->consume();
505
506 return true;
507 } elseif ($ch == 'B') {
508 $r->consume();
509
510 return false;
511 } elseif ($ch == 'n') {
512 $r->consume();
513
514 return null;
515 } else {
516 $r->error("Expected value, got '".$ch."'");
517 }
518 }
519
520 public function parseArrayValue($r, $stringTable, $objectShapeTable)
521 {
522 $r->skip('[');
523 if ($r->peek() == ']') {
524 $r->consume();
525
526 return [];
527 }
528
529 $arr = [];
530 while (true) {
531 $arr[] = $this->parseValue($r, $stringTable, $objectShapeTable);
532 $ch = $r->peek();
533 if ($ch == ']') {
534 $r->consume();
535
536 return $arr;
537 } elseif ($ch == ',') {
538 $r->consume();
539 }
540 }
541 }
542
543 public function parseShapedObjectValue($r, $stringTable, $objectShapeTable)
544 {
545 $r->skip('(');
546 $shapeId = $this->parseBase62($r);
547 if ($shapeId >= count($objectShapeTable)) {
548 $r->error('Shape ID '.$shapeId.' out of range');
549 }
550
551 $shape = $objectShapeTable[$shapeId];
552 $obj = [];
553 foreach ($shape as $key) {
554 if ($r->peek() == ',') {
555 $r->consume();
556 }
557 $obj[$key] = $this->parseValue($r, $stringTable, $objectShapeTable);
558 }
559
560 $r->skip(')');
561
562 return $obj;
563 }
564
565 public function parseKeyedObjectValue($r, $stringTable, $objectShapeTable)
566 {
567 $r->skip('{');
568 if ($r->peek() == '}') {
569 $r->consume();
570
571 return [];
572 }
573
574 $obj = [];
575 while (true) {
576 $key = $this->parseObjectKey($r, $stringTable);
577 if ($r->peek() == ':') {
578 $r->consume();
579 }
580
581 $obj[$key] = $this->parseValue($r, $stringTable, $objectShapeTable);
582 $ch = $r->peek();
583 if ($ch == ',') {
584 $r->consume();
585 } elseif ($ch == '}') {
586 $r->consume();
587
588 return $obj;
589 }
590 }
591 }
592
593 public function parseNumberValue($r)
594 {
595 $ch = $r->peek();
596 if ($ch == 'i') {
597 $r->consume();
598
599 return $this->parseBase62($r);
600 } elseif ($ch == 'I') {
601 $r->consume();
602
603 return -$this->parseBase62($r);
604 } else {
605 return $this->parseFloatValue($r);
606 }
607 }
608
609 public function parseFloatValue($r)
610 {
611 // Here, we read the float, but then use custom's float parser,
612 // because making a float parser and serializer pair
613 // which can round-trip any number is apparently pretty hard
614
615 $str = '';
616
617 if (($ch = $r->peek()) == '-') {
618 $str .= $ch;
619 $r->consume();
620 }
621
622 while (preg_match('/[0-9]/', $ch = $r->peek())) {
623 $str .= $ch;
624 $r->consume();
625 }
626
627 if ($str == '' || $str == '-') {
628 $r->error('Zero-length number in float literal');
629 }
630
631 if (($ch = $r->peek()) == '.') {
632 $str .= $ch;
633 $r->consume();
634
635 while (preg_match('/[0-9]/', $ch = $r->peek())) {
636 $str .= $ch;
637 $r->consume();
638 }
639
640 if (substr($str, strlen($str) - 1, 1) == '.') {
641 $r->error('Zero-length fractional part in float literal');
642 }
643 }
644
645 $ch = $r->peek();
646 if ($ch == 'e' || $ch == 'E') {
647 $str .= $ch;
648 $r->consume();
649
650 $ch = $r->peek();
651 if ($ch == '+' || $ch == '-') {
652 $str .= $ch;
653 $r->consume();
654 }
655
656 while (preg_match('/[0-9]/', $ch = $r->peek())) {
657 $str .= $ch;
658 $r->consume();
659 }
660
661 if (! preg_match('/[0-9]/', substr($str, strlen($str) - 1, 1))) {
662 $r->error('Zero-length exponential part in float literal');
663 }
664 }
665
666 return $this->stringToFloat($str);
667 }
668
669 public function stringToFloat($str)
670 {
671 $isNegative = false;
672 if (substr($str, 0, 1) === '-') {
673 $isNegative = true;
674 $str = substr($str, 1);
675 }
676
677 $parts = explode('.', $str);
678 $decimalLength = isset($parts[1]) ? strlen($parts[1]) : 0;
679
680 $floatValue = (float) $str;
681
682 $formattedValue = number_format($floatValue, $decimalLength, '.', '');
683
684 return $isNegative ? -1 * $formattedValue : $formattedValue;
685 }
686
687 public function parseStringValue($r, $stringTable)
688 {
689 if ($r->peek() == '"') {
690 return $this->parseJsonString($r);
691 } else {
692 $r->skip('s');
693 $id = $this->parseBase62($r);
694 if ($id >= count($stringTable)) {
695 $r->error('String ID '.$id.' out of range');
696 }
697
698 return $stringTable[$id];
699 }
700 }
701 }
702