001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.tool;
025
026import java.io.CharArrayWriter;
027import java.io.IOException;
028import java.io.Reader;
029import java.io.Writer;
030import java.util.Arrays;
031
032final class CodeEscapeUtil {
033
034
035    private static final char ESCAPE_PREFIX = '\\';
036    private static final char ESCAPE_UHEXA_PREFIX2 = 'u';
037    private static final char[] ESCAPE_UHEXA_PREFIX = "\\u".toCharArray();
038
039    private static char[] HEXA_CHARS_UPPER = "0123456789ABCDEF".toCharArray();
040    private static char[] HEXA_CHARS_LOWER = "0123456789abcdef".toCharArray();
041
042    private static int SEC_CHARS_LEN = '\\' + 1;
043    private static char SEC_CHARS_NO_SEC = '*';
044    private static char[] SEC_CHARS;
045
046    private static final char ESCAPE_LEVELS_LEN = 0x9f + 2;
047    private static final byte[] ESCAPE_LEVELS;
048
049
050
051    static {
052
053        SEC_CHARS = new char[SEC_CHARS_LEN];
054        Arrays.fill(SEC_CHARS,SEC_CHARS_NO_SEC);
055        SEC_CHARS[0x08] = 'b';
056        SEC_CHARS[0x09] = 't';
057        SEC_CHARS[0x0A] = 'n';
058        SEC_CHARS[0x0C] = 'f';
059        SEC_CHARS[0x0D] = 'r';
060        SEC_CHARS[0x22] = '"';
061
062        SEC_CHARS[0x27] = '\'';
063        SEC_CHARS[0x5C] = '\\';
064
065        ESCAPE_LEVELS = new byte[ESCAPE_LEVELS_LEN];
066
067        Arrays.fill(ESCAPE_LEVELS, (byte)3);
068
069        for (char c = 0x80; c < ESCAPE_LEVELS_LEN; c++) {
070            ESCAPE_LEVELS[c] = 2;
071        }
072
073        for (char c = 'A'; c <= 'Z'; c++) {
074            ESCAPE_LEVELS[c] = 4;
075        }
076        for (char c = 'a'; c <= 'z'; c++) {
077            ESCAPE_LEVELS[c] = 4;
078        }
079        for (char c = '0'; c <= '9'; c++) {
080            ESCAPE_LEVELS[c] = 4;
081        }
082
083        ESCAPE_LEVELS[0x08] = 1;
084        ESCAPE_LEVELS[0x09] = 1;
085        ESCAPE_LEVELS[0x0A] = 1;
086        ESCAPE_LEVELS[0x0C] = 1;
087        ESCAPE_LEVELS[0x0D] = 1;
088        ESCAPE_LEVELS[0x22] = 1;
089
090        ESCAPE_LEVELS[0x27] = 3;
091        ESCAPE_LEVELS[0x5C] = 1;
092
093        for (char c = 0x00; c <= 0x1F; c++) {
094            ESCAPE_LEVELS[c] = 1;
095        }
096        for (char c = 0x7F; c <= 0x9F; c++) {
097            ESCAPE_LEVELS[c] = 1;
098        }
099
100    }
101
102
103
104    private CodeEscapeUtil() {
105        super();
106    }
107
108    static char[] toUHexa(final int codepoint) {
109        final char[] result = new char[4];
110        result[3] = HEXA_CHARS_UPPER[codepoint % 0x10];
111        result[2] = HEXA_CHARS_UPPER[(codepoint >>> 4) % 0x10];
112        result[1] = HEXA_CHARS_UPPER[(codepoint >>> 8) % 0x10];
113        result[0] = HEXA_CHARS_UPPER[(codepoint >>> 12) % 0x10];
114        return result;
115    }
116
117    static String escape(final String text, final CodeEscapeLevel escapeLevel) {
118
119        if (text == null) {
120            return null;
121        }
122
123        final int level = escapeLevel.getEscapeLevel();
124
125        StringBuilder strBuilder = null;
126
127        final int offset = 0;
128        final int max = text.length();
129
130        int readOffset = offset;
131
132        for (int i = offset; i < max; i++) {
133
134            final int codepoint = Character.codePointAt(text, i);
135
136            if (codepoint <= (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[codepoint]) {
137                continue;
138            }
139
140            if (codepoint > (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[ESCAPE_LEVELS_LEN - 1]) {
141
142                if (Character.charCount(codepoint) > 1) {
143                    i++;
144                }
145
146                continue;
147
148            }
149
150            if (strBuilder == null) {
151                strBuilder = new StringBuilder(max + 20);
152            }
153
154            if (i - readOffset > 0) {
155                strBuilder.append(text, readOffset, i);
156            }
157
158            if (Character.charCount(codepoint) > 1) {
159                i++;
160            }
161
162            readOffset = i + 1;
163
164
165
166
167            if (codepoint < SEC_CHARS_LEN) {
168
169                final char sec = SEC_CHARS[codepoint];
170
171                if (sec != SEC_CHARS_NO_SEC) {
172
173                    strBuilder.append(ESCAPE_PREFIX);
174                    strBuilder.append(sec);
175                    continue;
176                }
177
178            }
179
180            if (Character.charCount(codepoint) > 1) {
181                final char[] codepointChars = Character.toChars(codepoint);
182                strBuilder.append(ESCAPE_UHEXA_PREFIX);
183                strBuilder.append(toUHexa(codepointChars[0]));
184                strBuilder.append(ESCAPE_UHEXA_PREFIX);
185                strBuilder.append(toUHexa(codepointChars[1]));
186                continue;
187            }
188
189            strBuilder.append(ESCAPE_UHEXA_PREFIX);
190            strBuilder.append(toUHexa(codepoint));
191
192        }
193
194        if (strBuilder == null) {
195            return text;
196        }
197
198        if (max - readOffset > 0) {
199            strBuilder.append(text, readOffset, max);
200        }
201
202        return strBuilder.toString();
203
204    }
205
206    static void escape(final Reader reader, final Writer writer, final CodeEscapeLevel escapeLevel)
207            throws IOException {
208
209        if (reader == null) {
210            return;
211        }
212
213        final int level = escapeLevel.getEscapeLevel();
214
215        int c1, c2;
216
217        c2 = reader.read();
218
219        while (c2 >= 0) {
220
221            c1 = c2;
222            c2 = reader.read();
223
224            final int codepoint = codePointAt((char)c1, (char)c2);
225
226            if (codepoint <= (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[codepoint]) {
227                writer.write(c1);
228                continue;
229            }
230
231            if (codepoint > (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[ESCAPE_LEVELS_LEN - 1]) {
232
233                writer.write(c1);
234
235                if (Character.charCount(codepoint) > 1) {
236                    writer.write(c2);
237
238                    c1 = c2;
239                    c2 = reader.read();
240
241                }
242
243                continue;
244
245            }
246
247
248            if (Character.charCount(codepoint) > 1) {
249                c1 = c2;
250                c2 = reader.read();
251            }
252
253            if (codepoint < SEC_CHARS_LEN) {
254
255                final char sec = SEC_CHARS[codepoint];
256
257                if (sec != SEC_CHARS_NO_SEC) {
258                    writer.write(ESCAPE_PREFIX);
259                    writer.write(sec);
260                    continue;
261                }
262
263            }
264
265            if (Character.charCount(codepoint) > 1) {
266                final char[] codepointChars = Character.toChars(codepoint);
267                writer.write(ESCAPE_UHEXA_PREFIX);
268                writer.write(toUHexa(codepointChars[0]));
269                writer.write(ESCAPE_UHEXA_PREFIX);
270                writer.write(toUHexa(codepointChars[1]));
271                continue;
272            }
273
274            writer.write(ESCAPE_UHEXA_PREFIX);
275            writer.write(toUHexa(codepoint));
276
277        }
278
279    }
280
281    static void escape(final char[] text, final int offset, final int len, final Writer writer,
282                       final CodeEscapeLevel escapeLevel)
283                       throws IOException {
284
285        if (text == null || text.length == 0) {
286            return;
287        }
288
289        final int level = escapeLevel.getEscapeLevel();
290
291        final int max = (offset + len);
292
293        int readOffset = offset;
294
295        for (int i = offset; i < max; i++) {
296
297            final int codepoint = Character.codePointAt(text, i);
298
299            if (codepoint <= (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[codepoint]) {
300                continue;
301            }
302
303            if (codepoint > (ESCAPE_LEVELS_LEN - 2) && level < ESCAPE_LEVELS[ESCAPE_LEVELS_LEN - 1]) {
304
305                if (Character.charCount(codepoint) > 1) {
306                    i++;
307                }
308
309                continue;
310
311            }
312
313            if (i - readOffset > 0) {
314                writer.write(text, readOffset, (i - readOffset));
315            }
316
317            if (Character.charCount(codepoint) > 1) {
318                i++;
319            }
320
321            readOffset = i + 1;
322
323            if (codepoint < SEC_CHARS_LEN) {
324
325                final char sec = SEC_CHARS[codepoint];
326
327                if (sec != SEC_CHARS_NO_SEC) {
328                    writer.write(ESCAPE_PREFIX);
329                    writer.write(sec);
330                    continue;
331                }
332
333            }
334
335            if (Character.charCount(codepoint) > 1) {
336                final char[] codepointChars = Character.toChars(codepoint);
337                writer.write(ESCAPE_UHEXA_PREFIX);
338                writer.write(toUHexa(codepointChars[0]));
339                writer.write(ESCAPE_UHEXA_PREFIX);
340                writer.write(toUHexa(codepointChars[1]));
341                continue;
342            }
343
344            writer.write(ESCAPE_UHEXA_PREFIX);
345            writer.write(toUHexa(codepoint));
346
347        }
348
349        if (max - readOffset > 0) {
350            writer.write(text, readOffset, (max - readOffset));
351        }
352
353    }
354    
355    static int parseIntFromReference(final String text, final int start, final int end, final int radix) {
356        int result = 0;
357        for (int i = start; i < end; i++) {
358            final char c = text.charAt(i);
359            int n = -1;
360            for (int j = 0; j < HEXA_CHARS_UPPER.length; j++) {
361                if (c == HEXA_CHARS_UPPER[j] || c == HEXA_CHARS_LOWER[j]) {
362                    n = j;
363                    break;
364                }
365            }
366            result = (radix * result) + n;
367        }
368        return result;
369    }
370
371    static int parseIntFromReference(final char[] text, final int start, final int end, final int radix) {
372        int result = 0;
373        for (int i = start; i < end; i++) {
374            final char c = text[i];
375            int n = -1;
376            for (int j = 0; j < HEXA_CHARS_UPPER.length; j++) {
377                if (c == HEXA_CHARS_UPPER[j] || c == HEXA_CHARS_LOWER[j]) {
378                    n = j;
379                    break;
380                }
381            }
382            result = (radix * result) + n;
383        }
384        return result;
385    }
386
387    static boolean isOctalEscape(final String text, final int start, final int end) {
388
389        if (start >= end) {
390            return false;
391        }
392
393        final char c1 = text.charAt(start);
394        if (c1 < '0' || c1 > '7') {
395            return false;
396        }
397
398        if (start + 1 >= end) {
399            return (c1 != '0'); 
400        }
401
402        final char c2 = text.charAt(start + 1);
403        if (c2 < '0' || c2 > '7') {
404            return (c1 != '0');
405        }
406
407        if (start + 2 >= end) {
408            return (c1 != '0' || c2 != '0'); 
409        }
410
411        final char c3 = text.charAt(start + 2);
412        if (c3 < '0' || c3 > '7') {
413            return (c1 != '0' || c2 != '0');
414        }
415
416        return (c1 != '0' || c2 != '0' || c3 != '0'); 
417
418    }
419
420
421    static boolean isOctalEscape(final char[] text, final int start, final int end) {
422
423        if (start >= end) {
424            return false;
425        }
426
427        final char c1 = text[start];
428        if (c1 < '0' || c1 > '7') {
429            return false;
430        }
431
432        if (start + 1 >= end) {
433            return (c1 != '0');
434        }
435
436        final char c2 = text[start + 1];
437        if (c2 < '0' || c2 > '7') {
438            return (c1 != '0'); 
439        }
440
441        if (start + 2 >= end) {
442            return (c1 != '0' || c2 != '0'); 
443        }
444
445        final char c3 = text[start + 2];
446        if (c3 < '0' || c3 > '7') {
447            return (c1 != '0' || c2 != '0'); 
448        }
449
450        return (c1 != '0' || c2 != '0' || c3 != '0'); 
451
452    }
453
454    static String unicodeUnescape(final String text) {
455
456        if (text == null) {
457            return null;
458        }
459
460        StringBuilder strBuilder = null;
461
462        final int offset = 0;
463        final int max = text.length();
464
465        int readOffset = offset;
466        int referenceOffset = offset;
467
468        for (int i = offset; i < max; i++) {
469
470            final char c = text.charAt(i);
471
472
473            if (c != ESCAPE_PREFIX || (i + 1) >= max) {
474                continue;
475            }
476
477            int codepoint = -1;
478
479            if (c == ESCAPE_PREFIX) {
480
481                final char c1 = text.charAt(i + 1);
482
483                if (c1 == ESCAPE_UHEXA_PREFIX2) {
484
485                    int f = i + 2;
486
487                    while (f < max) {
488                        final char cf = text.charAt(f);
489                        if (cf != ESCAPE_UHEXA_PREFIX2) {
490                            break;
491                        }
492                        f++;
493                    }
494                    int s = f;
495
496                    while (f < (s + 4) && f < max) {
497                        final char cf = text.charAt(f);
498                        if (!((cf >= '0' && cf <= '9') || (cf >= 'A' && cf <= 'F') || (cf >= 'a' && cf <= 'f'))) {
499                            break;
500                        }
501                        f++;
502                    }
503
504                    if ((f - s) < 4) {
505                        i++;
506                        continue;
507                    }
508
509                    codepoint = parseIntFromReference(text, s, f, 16);
510
511                    referenceOffset = f - 1;
512
513                } else {
514
515                    i++;
516                    continue;
517
518                }
519
520            }
521
522            if (strBuilder == null) {
523                strBuilder = new StringBuilder(max + 5);
524            }
525
526            if (i - readOffset > 0) {
527                strBuilder.append(text, readOffset, i);
528            }
529
530            i = referenceOffset;
531            readOffset = i + 1;
532
533            if (codepoint > '\uFFFF') {
534                strBuilder.append(Character.toChars(codepoint));
535            } else {
536                strBuilder.append((char)codepoint);
537            }
538
539        }
540
541
542        if (strBuilder == null) {
543            return text;
544        }
545
546        if (max - readOffset > 0) {
547            strBuilder.append(text, readOffset, max);
548        }
549
550        return strBuilder.toString();
551
552    }
553
554
555
556    static boolean requiresUnicodeUnescape(final char[] text, final int offset, final int len) {
557
558        if (text == null) {
559            return false;
560        }
561
562        final int max = (offset + len);
563
564        for (int i = offset; i < max; i++) {
565
566            final char c = text[i];
567
568            if (c != ESCAPE_PREFIX || (i + 1) >= max) {
569                continue;
570            }
571
572            if (c == ESCAPE_PREFIX) {
573
574                final char c1 = text[i + 1];
575
576                if (c1 == ESCAPE_UHEXA_PREFIX2) {
577                    return true;
578                }
579
580            }
581
582        }
583
584        return false;
585
586    }
587
588
589
590    static void unicodeUnescape(final char[] text, final int offset, final int len, final Writer writer)
591                                throws IOException {
592
593        if (text == null) {
594            return;
595        }
596
597        final int max = (offset + len);
598
599        int readOffset = offset;
600        int referenceOffset = offset;
601
602        for (int i = offset; i < max; i++) {
603
604            final char c = text[i];
605
606            if (c != ESCAPE_PREFIX || (i + 1) >= max) {
607                continue;
608            }
609
610            int codepoint = -1;
611
612            if (c == ESCAPE_PREFIX) {
613
614                final char c1 = text[i + 1];
615
616                if (c1 == ESCAPE_UHEXA_PREFIX2) {
617
618                    int f = i + 2;
619
620                    while (f < max) {
621                        final char cf = text[f];
622                        if (cf != ESCAPE_UHEXA_PREFIX2) {
623                            break;
624                        }
625                        f++;
626                    }
627                    int s = f;
628
629                    while (f < (s + 4) && f < max) {
630                        final char cf = text[f];
631                        if (!((cf >= '0' && cf <= '9') || (cf >= 'A' && cf <= 'F') || (cf >= 'a' && cf <= 'f'))) {
632                            break;
633                        }
634                        f++;
635                    }
636
637                    if ((f - s) < 4) {
638                        i++;
639                        continue;
640                    }
641
642                    codepoint = parseIntFromReference(text, s, f, 16);
643
644                    referenceOffset = f - 1;
645
646
647                } else {
648                    i++;
649                    continue;
650
651                }
652
653            }
654
655            if (i - readOffset > 0) {
656                writer.write(text, readOffset, (i - readOffset));
657            }
658
659            i = referenceOffset;
660            readOffset = i + 1;
661
662            if (codepoint > '\uFFFF') {
663                writer.write(Character.toChars(codepoint));
664            } else {
665                writer.write((char) codepoint);
666            }
667
668        }
669
670
671        if (max - readOffset > 0) {
672            writer.write(text, readOffset, (max - readOffset));
673        }
674
675    }
676
677    static String unescape(final String text) {
678
679        if (text == null) {
680            return null;
681        }
682
683        final String unicodeEscapedText = unicodeUnescape(text);
684
685        StringBuilder strBuilder = null;
686
687        final int offset = 0;
688        final int max = unicodeEscapedText.length();
689
690        int readOffset = offset;
691        int referenceOffset = offset;
692
693        for (int i = offset; i < max; i++) {
694
695            final char c = unicodeEscapedText.charAt(i);
696
697            if (c != ESCAPE_PREFIX || (i + 1) >= max) {
698                continue;
699            }
700
701            int codepoint = -1;
702
703            if (c == ESCAPE_PREFIX) {
704
705                final char c1 = unicodeEscapedText.charAt(i + 1);
706
707                switch (c1) {
708                    case '0': if (!isOctalEscape(unicodeEscapedText,i + 1,max)) { codepoint = 0x00; referenceOffset = i + 1; }; break;
709                    case 'b': codepoint = 0x08; referenceOffset = i + 1; break;
710                    case 't': codepoint = 0x09; referenceOffset = i + 1; break;
711                    case 'n': codepoint = 0x0A; referenceOffset = i + 1; break;
712                    case 'f': codepoint = 0x0C; referenceOffset = i + 1; break;
713                    case 'r': codepoint = 0x0D; referenceOffset = i + 1; break;
714                    case '"': codepoint = 0x22; referenceOffset = i + 1; break;
715                    case '\'': codepoint = 0x27; referenceOffset = i + 1; break;
716                    case '\\': codepoint = 0x5C; referenceOffset = i + 1; break;
717                }
718
719                if (codepoint == -1) {
720
721                    if (c1 >= '0' && c1 <= '7') {
722
723                        int f = i + 2;
724                        while (f < (i + 4) && f < max) {
725                            final char cf = unicodeEscapedText.charAt(f);
726                            if (!(cf >= '0' && cf <= '7')) {
727                                break;
728                            }
729                            f++;
730                        }
731
732                        codepoint = parseIntFromReference(unicodeEscapedText, i + 1, f, 8);
733
734                        if (codepoint > 0xFF) {
735                            codepoint = parseIntFromReference(unicodeEscapedText, i + 1, f - 1, 8);
736                            referenceOffset = f - 2;
737                        } else {
738                            referenceOffset = f - 1;
739                        }
740
741                    } else {
742                        i++;
743                        continue;
744
745                    }
746
747                }
748
749            }
750            
751            if (strBuilder == null) {
752                strBuilder = new StringBuilder(max + 5);
753            }
754
755            if (i - readOffset > 0) {
756                strBuilder.append(unicodeEscapedText, readOffset, i);
757            }
758
759            i = referenceOffset;
760            readOffset = i + 1;
761
762            if (codepoint > '\uFFFF') {
763                strBuilder.append(Character.toChars(codepoint));
764            } else {
765                strBuilder.append((char)codepoint);
766            }
767
768        }
769
770        if (strBuilder == null) {
771            return unicodeEscapedText;
772        }
773
774        if (max - readOffset > 0) {
775            strBuilder.append(unicodeEscapedText, readOffset, max);
776        }
777
778        return strBuilder.toString();
779
780    }
781
782    static void unescape(final Reader reader, final Writer writer) throws IOException {
783
784        if (reader == null) {
785            return;
786        }
787        
788        char[] buffer = new char[20];
789
790        int read = reader.read(buffer, 0, buffer.length);
791        if (read < 0) {
792            return;
793        }
794
795        int bufferSize = read;
796
797        while (bufferSize > 0 || read >= 0) {
798
799            int nonEscCounter = 0;
800
801            int n = bufferSize;
802            while (nonEscCounter < 8 && n-- != 0) {
803                if (buffer[n] == '\\') {
804                    nonEscCounter = 0;
805                    continue;
806                }
807                nonEscCounter++;
808            }
809
810            if (nonEscCounter < 8 && read >= 0) {
811
812                if (bufferSize == buffer.length) {
813                    final char[] newBuffer = new char[buffer.length + (buffer.length / 2)];
814                    System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
815                    buffer = newBuffer;
816                }
817
818                read = reader.read(buffer, bufferSize, (buffer.length - bufferSize));
819                if (read >= 0) {
820                    bufferSize += read;
821                }
822
823                continue;
824
825            }
826
827            n = (n < 0? bufferSize : n + nonEscCounter);
828
829            unescape(buffer, 0, n, writer);
830
831            System.arraycopy(buffer, n, buffer, 0, (bufferSize - n));
832            bufferSize -= n;
833
834            read = reader.read(buffer, bufferSize, (buffer.length - bufferSize));
835            if (read >= 0) {
836                bufferSize += read;
837            }
838
839        }
840
841    }
842
843    static void unescape(final char[] text, final int offset, final int len, final Writer writer)
844                         throws IOException {
845
846        if (text == null) {
847            return;
848        }
849
850        char[] unicodeEscapedText = text;
851        int unicodeEscapedOffset = offset;
852        int unicodeEscapedLen = len;
853        if (requiresUnicodeUnescape(text, offset, len)) {
854            final CharArrayWriter charArrayWriter = new CharArrayWriter(len + 2);
855            unicodeUnescape(text, offset, len, charArrayWriter);
856            unicodeEscapedText = charArrayWriter.toCharArray();
857            unicodeEscapedOffset = 0;
858            unicodeEscapedLen = unicodeEscapedText.length;
859        }
860
861        final int max = (unicodeEscapedOffset + unicodeEscapedLen);
862
863        int readOffset = unicodeEscapedOffset;
864        int referenceOffset = unicodeEscapedOffset;
865
866        for (int i = unicodeEscapedOffset; i < max; i++) {
867
868            final char c = unicodeEscapedText[i];
869
870            if (c != ESCAPE_PREFIX || (i + 1) >= max) {
871                continue;
872            }
873
874            int codepoint = -1;
875
876            if (c == ESCAPE_PREFIX) {
877
878                final char c1 = unicodeEscapedText[i + 1];
879
880                switch (c1) {
881                    case '0': if (!isOctalEscape(unicodeEscapedText,i + 1,max)) { codepoint = 0x00; referenceOffset = i + 1; }; break;
882                    case 'b': codepoint = 0x08; referenceOffset = i + 1; break;
883                    case 't': codepoint = 0x09; referenceOffset = i + 1; break;
884                    case 'n': codepoint = 0x0A; referenceOffset = i + 1; break;
885                    case 'f': codepoint = 0x0C; referenceOffset = i + 1; break;
886                    case 'r': codepoint = 0x0D; referenceOffset = i + 1; break;
887                    case '"': codepoint = 0x22; referenceOffset = i + 1; break;
888                    case '\'': codepoint = 0x27; referenceOffset = i + 1; break;
889                    case '\\': codepoint = 0x5C; referenceOffset = i + 1; break;
890                }
891
892                if (codepoint == -1) {
893
894                    if (c1 >= '0' && c1 <= '7') {
895                        
896                        int f = i + 2;
897                        while (f < (i + 4) && f < max) { 
898                            final char cf = unicodeEscapedText[f];
899                            if (!(cf >= '0' && cf <= '7')) {
900                                break;
901                            }
902                            f++;
903                        }
904
905                        codepoint = parseIntFromReference(unicodeEscapedText, i + 1, f, 8);
906
907                        if (codepoint > 0xFF) {
908                            codepoint = parseIntFromReference(unicodeEscapedText, i + 1, f - 1, 8);
909                            referenceOffset = f - 2;
910                        } else {
911                            referenceOffset = f - 1;
912                        }
913                    } else {
914                        i++;
915                        continue;
916
917                    }
918
919                }
920
921            }
922
923            if (i - readOffset > 0) {
924                writer.write(unicodeEscapedText, readOffset, (i - readOffset));
925            }
926
927            i = referenceOffset;
928            readOffset = i + 1;
929
930            if (codepoint > '\uFFFF') {
931                writer.write(Character.toChars(codepoint));
932            } else {
933                writer.write((char)codepoint);
934            }
935
936        }
937
938        if (max - readOffset > 0) {
939            writer.write(unicodeEscapedText, readOffset, (max - readOffset));
940        }
941
942
943    }
944
945
946
947
948    private static int codePointAt(final char c1, final char c2) {
949        if (Character.isHighSurrogate(c1)) {
950            if (c2 >= 0) {
951                if (Character.isLowSurrogate(c2)) {
952                    return Character.toCodePoint(c1, c2);
953                }
954            }
955        }
956        return c1;
957    }
958
959
960
961}