1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.util;
16
17 import java.nio.charset.Charset;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 import org.htmlunit.html.impl.Color;
25
26
27
28
29
30
31
32
33
34 public final class StringUtils {
35
36
37
38
39 public static final String EMPTY_STRING = "";
40
41 private static final Pattern HEX_COLOR = Pattern.compile("#([\\da-fA-F]{3}|[\\da-fA-F]{6})");
42 private static final Pattern RGB_COLOR =
43 Pattern.compile("rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
44 + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
45 + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*\\)");
46 private static final Pattern RGBA_COLOR =
47 Pattern.compile("rgba\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
48 + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
49 + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
50 + "\\s*((0?.[1-9])|[01])\\s*\\)");
51 private static final Pattern HSL_COLOR =
52 Pattern.compile("hsl\\(\\s*((0|[1-9]\\d?|[12]\\d\\d?|3[0-5]\\d)(.\\d*)?)\\s*,"
53 + "\\s*((0|[1-9]\\d?|100)(.\\d*)?)%\\s*,"
54 + "\\s*((0|[1-9]\\d?|100)(.\\d*)?)%\\s*\\)");
55 private static final Pattern ILLEGAL_FILE_NAME_CHARS = Pattern.compile("\\\\|/|\\||:|\\?|\\*|\"|<|>|\\p{Cntrl}");
56
57 private static final Map<String, String> CAMELIZE_CACHE = new ConcurrentHashMap<>();
58
59
60
61
62 private StringUtils() {
63
64 }
65
66
67
68
69
70
71
72
73
74 public static boolean isEmptyString(final CharSequence s) {
75 return s != null && s.length() == 0;
76 }
77
78
79
80
81
82
83
84 public static boolean isEmptyOrNull(final CharSequence s) {
85 return s == null || s.length() == 0;
86 }
87
88
89
90
91
92
93
94
95
96
97 public static <T extends CharSequence> T defaultIfEmptyOrNull(final T s, final T defaultString) {
98 return isEmptyOrNull(s) ? defaultString : s;
99 }
100
101
102
103
104
105
106
107 public static boolean isBlank(final CharSequence s) {
108 if (s == null) {
109 return true;
110 }
111
112 final int length = s.length();
113 if (length == 0) {
114 return true;
115 }
116
117 for (int i = 0; i < length; i++) {
118 if (!Character.isWhitespace(s.charAt(i))) {
119 return false;
120 }
121 }
122 return true;
123 }
124
125
126
127
128
129
130
131 public static boolean isNotBlank(final CharSequence s) {
132 if (s == null) {
133 return false;
134 }
135
136 final int length = s.length();
137 if (length == 0) {
138 return false;
139 }
140
141 for (int i = 0; i < length; i++) {
142 if (!Character.isWhitespace(s.charAt(i))) {
143 return true;
144 }
145 }
146 return false;
147 }
148
149
150
151
152
153
154 public static boolean equalsChar(final char expected, final CharSequence s) {
155 return s != null && s.length() == 1 && expected == s.charAt(0);
156 }
157
158
159
160
161
162
163
164
165 public static boolean startsWithIgnoreCase(final String s, final String expectedStart) {
166 if (expectedStart == null || expectedStart.length() == 0) {
167 throw new IllegalArgumentException("Expected start string can't be null or empty");
168 }
169
170 if (s == null) {
171 return false;
172 }
173 if (s == expectedStart) {
174 return true;
175 }
176
177 return s.regionMatches(true, 0, expectedStart, 0, expectedStart.length());
178 }
179
180
181
182
183
184
185
186
187 public static boolean endsWithIgnoreCase(final String s, final String expectedEnd) {
188 if (expectedEnd == null) {
189 throw new IllegalArgumentException("Expected end string can't be null or empty");
190 }
191
192 final int expectedEndLength = expectedEnd.length();
193 if (expectedEndLength == 0) {
194 throw new IllegalArgumentException("Expected end string can't be null or empty");
195 }
196
197 if (s == null) {
198 return false;
199 }
200 if (s == expectedEnd) {
201 return true;
202 }
203
204 return s.regionMatches(true, s.length() - expectedEndLength, expectedEnd, 0, expectedEndLength);
205 }
206
207
208
209
210
211
212
213
214 public static boolean containsIgnoreCase(final String s, final String expected) {
215 if (expected == null) {
216 throw new IllegalArgumentException("Expected string can't be null or empty");
217 }
218
219 final int expectedLength = expected.length();
220 if (expectedLength == 0) {
221 throw new IllegalArgumentException("Expected string can't be null or empty");
222 }
223
224 if (s == null) {
225 return false;
226 }
227 if (s == expected) {
228 return true;
229 }
230
231 final int max = s.length() - expectedLength;
232 for (int i = 0; i <= max; i++) {
233 if (s.regionMatches(true, i, expected, 0, expectedLength)) {
234 return true;
235 }
236 }
237 return false;
238 }
239
240
241
242
243
244
245
246
247
248
249 @SuppressWarnings("null")
250 public static String replaceChars(final String str, final String searchChars, final String replaceChars) {
251 if (isEmptyOrNull(str) || isEmptyOrNull(searchChars)) {
252 return str;
253 }
254
255 final int replaceCharsLength = replaceChars == null ? 0 : replaceChars.length();
256 final int strLength = str.length();
257
258 StringBuilder buf = null;
259 int i = 0;
260 for ( ; i < strLength; i++) {
261 final char ch = str.charAt(i);
262 final int index = searchChars.indexOf(ch);
263 if (index != -1) {
264 buf = new StringBuilder(strLength);
265 buf.append(str, 0, i);
266 if (index < replaceCharsLength) {
267 buf.append(replaceChars.charAt(index));
268 }
269 break;
270 }
271 }
272
273 if (buf == null) {
274 return str;
275 }
276
277 i++;
278 for ( ; i < strLength; i++) {
279 final char ch = str.charAt(i);
280 final int index = searchChars.indexOf(ch);
281 if (index != -1) {
282 if (index < replaceCharsLength) {
283 buf.append(replaceChars.charAt(index));
284 }
285 }
286 else {
287 buf.append(ch);
288 }
289 }
290
291 return buf.toString();
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 public static String substringAfter(final String str, final String find) {
308 if (isEmptyOrNull(str)) {
309 return str;
310 }
311 if (find == null) {
312 return EMPTY_STRING;
313 }
314 final int pos = str.indexOf(find);
315 if (pos == -1) {
316 return EMPTY_STRING;
317 }
318 return str.substring(pos + find.length());
319 }
320
321
322
323
324
325
326
327 public static String escapeXmlChars(final String s) {
328 return org.apache.commons.lang3.StringUtils.
329 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&", "<", ">"});
330 }
331
332
333
334
335
336
337
338 public static String escapeXml(final String text) {
339 if (text == null) {
340 return null;
341 }
342
343 StringBuilder escaped = null;
344
345 final int offset = 0;
346 final int max = text.length();
347
348 int readOffset = offset;
349
350 for (int i = offset; i < max; i++) {
351 final int codepoint = Character.codePointAt(text, i);
352 final boolean codepointValid = supportedByXML10(codepoint);
353
354 if (!codepointValid
355 || codepoint == '<'
356 || codepoint == '>'
357 || codepoint == '&'
358 || codepoint == '\''
359 || codepoint == '"') {
360
361
362 if (escaped == null) {
363 escaped = new StringBuilder(max);
364 }
365
366 if (i > readOffset) {
367 escaped.append(text, readOffset, i);
368 }
369
370 if (Character.charCount(codepoint) > 1) {
371 i++;
372 }
373 readOffset = i + 1;
374
375
376 if (!codepointValid) {
377 continue;
378 }
379
380 if (codepoint == '<') {
381 escaped.append("<");
382 }
383 else if (codepoint == '>') {
384 escaped.append(">");
385 }
386 else if (codepoint == '&') {
387 escaped.append("&");
388 }
389 else if (codepoint == '\'') {
390 escaped.append("'");
391 }
392 else if (codepoint == '\"') {
393 escaped.append(""");
394 }
395 }
396 }
397
398 if (escaped == null) {
399 return text;
400 }
401
402 if (max > readOffset) {
403 escaped.append(text, readOffset, max);
404 }
405
406 return escaped.toString();
407 }
408
409
410
411
412
413
414
415
416 public static String escapeXmlAttributeValue(final String attValue) {
417 if (attValue == null) {
418 return null;
419 }
420
421 StringBuilder escaped = null;
422
423 final int offset = 0;
424 final int max = attValue.length();
425
426 int readOffset = offset;
427
428 for (int i = offset; i < max; i++) {
429 final int codepoint = Character.codePointAt(attValue, i);
430 final boolean codepointValid = supportedByXML10(codepoint);
431
432 if (!codepointValid
433 || codepoint == '<'
434 || codepoint == '&'
435 || codepoint == '"') {
436
437
438 if (escaped == null) {
439 escaped = new StringBuilder(max);
440 }
441
442 if (i > readOffset) {
443 escaped.append(attValue, readOffset, i);
444 }
445
446 if (Character.charCount(codepoint) > 1) {
447 i++;
448 }
449 readOffset = i + 1;
450
451
452 if (!codepointValid) {
453 continue;
454 }
455
456 if (codepoint == '<') {
457 escaped.append("<");
458 }
459 else if (codepoint == '&') {
460 escaped.append("&");
461 }
462 else if (codepoint == '\"') {
463 escaped.append(""");
464 }
465 }
466 }
467
468 if (escaped == null) {
469 return attValue;
470 }
471
472 if (max > readOffset) {
473 escaped.append(attValue, readOffset, max);
474 }
475
476 return escaped.toString();
477 }
478
479
480
481
482
483
484 private static boolean supportedByXML10(final int codepoint) {
485 if (codepoint < 0x20) {
486 return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD;
487 }
488 if (codepoint <= 0xD7FF) {
489 return true;
490 }
491
492 if (codepoint < 0xE000) {
493 return false;
494 }
495 if (codepoint <= 0xFFFD) {
496 return true;
497 }
498
499 if (codepoint < 0x10000) {
500 return false;
501 }
502 if (codepoint <= 0x10FFFF) {
503 return true;
504 }
505
506 return true;
507 }
508
509
510
511
512
513
514
515
516
517
518
519 public static int indexOf(final String s, final char searchChar, final int beginIndex, final int endIndex) {
520 for (int i = beginIndex; i < endIndex; i++) {
521 if (s.charAt(i) == searchChar) {
522 return i;
523 }
524 }
525 return -1;
526 }
527
528
529
530
531
532
533 public static Color asColorHexadecimal(final String token) {
534 if (token == null) {
535 return null;
536 }
537 final Matcher tmpMatcher = HEX_COLOR.matcher(token);
538 final boolean tmpFound = tmpMatcher.matches();
539 if (!tmpFound) {
540 return null;
541 }
542
543 final String tmpHex = tmpMatcher.group(1);
544 if (tmpHex.length() == 6) {
545 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 2), 16);
546 final int tmpGreen = Integer.parseInt(tmpHex.substring(2, 4), 16);
547 final int tmpBlue = Integer.parseInt(tmpHex.substring(4, 6), 16);
548 return new Color(tmpRed, tmpGreen, tmpBlue);
549 }
550
551 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 1) + tmpHex.substring(0, 1), 16);
552 final int tmpGreen = Integer.parseInt(tmpHex.substring(1, 2) + tmpHex.substring(1, 2), 16);
553 final int tmpBlue = Integer.parseInt(tmpHex.substring(2, 3) + tmpHex.substring(2, 3), 16);
554 return new Color(tmpRed, tmpGreen, tmpBlue);
555 }
556
557
558
559
560
561
562 public static Color findColorRGB(final String token) {
563 if (token == null) {
564 return null;
565 }
566 final Matcher tmpMatcher = RGB_COLOR.matcher(token);
567 if (!tmpMatcher.find()) {
568 return null;
569 }
570
571 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
572 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
573 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
574 return new Color(tmpRed, tmpGreen, tmpBlue);
575 }
576
577
578
579
580
581
582 public static Color findColorRGBA(final String token) {
583 if (token == null) {
584 return null;
585 }
586 final Matcher tmpMatcher = RGBA_COLOR.matcher(token);
587 if (!tmpMatcher.find()) {
588 return null;
589 }
590
591 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
592 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
593 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
594 final int tmpAlpha = (int) (Float.parseFloat(tmpMatcher.group(4)) * 255);
595 return new Color(tmpRed, tmpGreen, tmpBlue, tmpAlpha);
596 }
597
598
599
600
601
602
603 public static Color findColorHSL(final String token) {
604 if (token == null) {
605 return null;
606 }
607 final Matcher tmpMatcher = HSL_COLOR.matcher(token);
608 if (!tmpMatcher.find()) {
609 return null;
610 }
611
612 final float tmpHue = Float.parseFloat(tmpMatcher.group(1)) / 360f;
613 final float tmpSaturation = Float.parseFloat(tmpMatcher.group(4)) / 100f;
614 final float tmpLightness = Float.parseFloat(tmpMatcher.group(7)) / 100f;
615 return hslToRgb(tmpHue, tmpSaturation, tmpLightness);
616 }
617
618
619
620
621
622
623
624
625
626
627
628 private static Color hslToRgb(final float h, final float s, final float l) {
629 if (s == 0f) {
630 return new Color(to255(l), to255(l), to255(l));
631 }
632
633 final float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
634 final float p = 2 * l - q;
635 final float r = hueToRgb(p, q, h + 1f / 3f);
636 final float g = hueToRgb(p, q, h);
637 final float b = hueToRgb(p, q, h - 1f / 3f);
638
639 return new Color(to255(r), to255(g), to255(b));
640 }
641
642 private static float hueToRgb(final float p, final float q, float t) {
643 if (t < 0f) {
644 t += 1f;
645 }
646
647 if (t > 1f) {
648 t -= 1f;
649 }
650
651 if (t < 1f / 6f) {
652 return p + (q - p) * 6f * t;
653 }
654
655 if (t < 1f / 2f) {
656 return q;
657 }
658
659 if (t < 2f / 3f) {
660 return p + (q - p) * (2f / 3f - t) * 6f;
661 }
662
663 return p;
664 }
665
666 private static int to255(final float value) {
667 return (int) Math.min(255, 256 * value);
668 }
669
670
671
672
673
674
675
676 public static String formatColor(final Color color) {
677 return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
678 }
679
680
681
682
683
684
685
686
687
688 public static String sanitizeForAppendReplacement(final String toSanitize) {
689 return org.apache.commons.lang3.StringUtils.replaceEach(toSanitize,
690 new String[] {"\\", "$"}, new String[]{"\\\\", "\\$"});
691 }
692
693
694
695
696
697
698
699
700 public static String sanitizeForFileName(final String toSanitize) {
701 return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
702 }
703
704
705
706
707
708
709
710 public static String cssCamelize(final String string) {
711 if (string == null) {
712 return null;
713 }
714
715 String result = CAMELIZE_CACHE.get(string);
716 if (null != result) {
717 return result;
718 }
719
720
721 final int pos = string.indexOf('-');
722 if (pos == -1 || pos == string.length() - 1) {
723
724 CAMELIZE_CACHE.put(string, string);
725 return string;
726 }
727
728 final StringBuilder builder = new StringBuilder(string);
729 builder.deleteCharAt(pos);
730 builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
731
732 int i = pos + 1;
733 while (i < builder.length() - 1) {
734 if (builder.charAt(i) == '-') {
735 builder.deleteCharAt(i);
736 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
737 }
738 i++;
739 }
740 result = builder.toString();
741 CAMELIZE_CACHE.put(string, result);
742
743 return result;
744 }
745
746
747
748
749
750
751
752
753 public static String toRootLowerCase(final String s) {
754 return s == null ? null : s.toLowerCase(Locale.ROOT);
755 }
756
757
758
759
760
761
762
763
764 public static String cssDeCamelize(final String string) {
765 if (string == null || string.isEmpty()) {
766 return string;
767 }
768
769 final StringBuilder builder = new StringBuilder();
770 for (int i = 0; i < string.length(); i++) {
771 final char ch = string.charAt(i);
772 if (Character.isUpperCase(ch)) {
773 builder.append('-').append(Character.toLowerCase(ch));
774 }
775 else {
776 builder.append(ch);
777 }
778 }
779 return builder.toString();
780 }
781
782
783
784
785
786
787
788
789 public static byte[] toByteArray(final String content, final Charset charset) {
790 if (content == null || content.isEmpty()) {
791 return new byte[0];
792 }
793
794 return content.getBytes(charset);
795 }
796
797
798
799
800
801
802
803
804
805 public static String[] splitAtJavaWhitespace(final String str) {
806 final String[] parts = org.apache.commons.lang3.StringUtils.split(str);
807 if (parts == null) {
808 return new String[0];
809 }
810 return parts;
811 }
812
813
814
815
816
817
818
819
820 public static String[] splitAtBlank(final String str) {
821 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ' ');
822 if (parts == null) {
823 return new String[0];
824 }
825 return parts;
826 }
827
828
829
830
831
832
833
834
835 public static String[] splitAtComma(final String str) {
836 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ',');
837 if (parts == null) {
838 return new String[0];
839 }
840 return parts;
841 }
842
843
844
845
846
847
848
849
850 public static String[] splitAtCommaOrBlank(final String str) {
851 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ", ");
852 if (parts == null) {
853 return new String[0];
854 }
855 return parts;
856 }
857
858
859
860
861
862
863
864
865
866
867
868 public static String substringBefore(final String str, final String find) {
869 if (isEmptyOrNull(find)) {
870 throw new IllegalArgumentException("'find' string parameter has to be not empty and not null");
871 }
872
873 if (isEmptyString(str)) {
874 return str;
875 }
876
877 final int pos = str.indexOf(find);
878 if (pos == -1) {
879 return str;
880 }
881 return str.substring(0, pos);
882 }
883
884
885
886
887
888
889
890
891
892 public static int toInt(final String str, final int defaultValue) {
893 try {
894 return Integer.parseInt(str);
895 }
896 catch (final RuntimeException e) {
897 return defaultValue;
898 }
899 }
900
901
902
903
904
905
906
907
908
909 public static float toFloat(final String str, final float defaultValue) {
910 try {
911 return Float.parseFloat(str);
912 }
913 catch (final RuntimeException e) {
914 return defaultValue;
915 }
916 }
917
918
919
920
921
922
923
924
925
926
927 public static String trimRight(final String str) {
928 if (isEmptyOrNull(str)) {
929 return str;
930 }
931
932 int end = str.length();
933 while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
934 end--;
935 }
936
937 if (end == str.length()) {
938 return str;
939 }
940
941 return str.substring(0, end);
942 }
943
944
945
946
947
948
949 public static boolean containsOnly(final CharSequence cs, final char... valid) {
950 if (valid == null || valid.length == 0) {
951 throw new IllegalArgumentException("Expected valid char[] can't be null or empty");
952 }
953 if (isEmptyOrNull(cs)) {
954 return false;
955 }
956
957 final int csLength = cs.length();
958 final int validLength = valid.length;
959 for (int i = 0; i < csLength; i++) {
960 final char testChar = cs.charAt(i);
961 int j = 0;
962 for ( ; j < validLength; j++) {
963 final char validChar = valid[j];
964 if (validChar == testChar) {
965 break;
966 }
967 }
968 if (j == validLength) {
969 return false;
970 }
971 }
972
973 return true;
974 }
975 }