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
308 public static String substringAfter(final String str, final String find) {
309 if (isEmptyOrNull(str)) {
310 return str;
311 }
312 if (find == null) {
313 return EMPTY_STRING;
314 }
315 final int pos = str.indexOf(find);
316 if (pos == -1) {
317 return EMPTY_STRING;
318 }
319 return str.substring(pos + find.length());
320 }
321
322
323
324
325
326
327
328 public static String escapeXmlChars(final String s) {
329 return org.apache.commons.lang3.StringUtils.
330 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&", "<", ">"});
331 }
332
333
334
335
336
337
338
339 public static String escapeXml(final String text) {
340 if (text == null) {
341 return null;
342 }
343
344 StringBuilder escaped = null;
345
346 final int offset = 0;
347 final int max = text.length();
348
349 int readOffset = offset;
350
351 for (int i = offset; i < max; i++) {
352 final int codepoint = Character.codePointAt(text, i);
353 final boolean codepointValid = supportedByXML10(codepoint);
354
355 if (!codepointValid
356 || codepoint == '<'
357 || codepoint == '>'
358 || codepoint == '&'
359 || codepoint == '\''
360 || codepoint == '"') {
361
362
363 if (escaped == null) {
364 escaped = new StringBuilder(max);
365 }
366
367 if (i > readOffset) {
368 escaped.append(text, readOffset, i);
369 }
370
371 if (Character.charCount(codepoint) > 1) {
372 i++;
373 }
374 readOffset = i + 1;
375
376
377 if (!codepointValid) {
378 continue;
379 }
380
381 if (codepoint == '<') {
382 escaped.append("<");
383 }
384 else if (codepoint == '>') {
385 escaped.append(">");
386 }
387 else if (codepoint == '&') {
388 escaped.append("&");
389 }
390 else if (codepoint == '\'') {
391 escaped.append("'");
392 }
393 else if (codepoint == '\"') {
394 escaped.append(""");
395 }
396 }
397 }
398
399 if (escaped == null) {
400 return text;
401 }
402
403 if (max > readOffset) {
404 escaped.append(text, readOffset, max);
405 }
406
407 return escaped.toString();
408 }
409
410
411
412
413
414
415
416
417 public static String escapeXmlAttributeValue(final String attValue) {
418 if (attValue == null) {
419 return null;
420 }
421
422 StringBuilder escaped = null;
423
424 final int offset = 0;
425 final int max = attValue.length();
426
427 int readOffset = offset;
428
429 for (int i = offset; i < max; i++) {
430 final int codepoint = Character.codePointAt(attValue, i);
431 final boolean codepointValid = supportedByXML10(codepoint);
432
433 if (!codepointValid
434 || codepoint == '<'
435 || codepoint == '&'
436 || codepoint == '"') {
437
438
439 if (escaped == null) {
440 escaped = new StringBuilder(max);
441 }
442
443 if (i > readOffset) {
444 escaped.append(attValue, readOffset, i);
445 }
446
447 if (Character.charCount(codepoint) > 1) {
448 i++;
449 }
450 readOffset = i + 1;
451
452
453 if (!codepointValid) {
454 continue;
455 }
456
457 if (codepoint == '<') {
458 escaped.append("<");
459 }
460 else if (codepoint == '&') {
461 escaped.append("&");
462 }
463 else if (codepoint == '\"') {
464 escaped.append(""");
465 }
466 }
467 }
468
469 if (escaped == null) {
470 return attValue;
471 }
472
473 if (max > readOffset) {
474 escaped.append(attValue, readOffset, max);
475 }
476
477 return escaped.toString();
478 }
479
480
481
482
483
484
485 private static boolean supportedByXML10(final int codepoint) {
486 if (codepoint < 0x20) {
487 return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD;
488 }
489 if (codepoint <= 0xD7FF) {
490 return true;
491 }
492
493 if (codepoint < 0xE000) {
494 return false;
495 }
496 if (codepoint <= 0xFFFD) {
497 return true;
498 }
499
500 if (codepoint < 0x10000) {
501 return false;
502 }
503 if (codepoint <= 0x10FFFF) {
504 return true;
505 }
506
507 return true;
508 }
509
510
511
512
513
514
515
516
517
518
519
520 public static int indexOf(final String s, final char searchChar, final int beginIndex, final int endIndex) {
521 for (int i = beginIndex; i < endIndex; i++) {
522 if (s.charAt(i) == searchChar) {
523 return i;
524 }
525 }
526 return -1;
527 }
528
529
530
531
532
533
534 public static Color asColorHexadecimal(final String token) {
535 if (token == null) {
536 return null;
537 }
538 final Matcher tmpMatcher = HEX_COLOR.matcher(token);
539 final boolean tmpFound = tmpMatcher.matches();
540 if (!tmpFound) {
541 return null;
542 }
543
544 final String tmpHex = tmpMatcher.group(1);
545 if (tmpHex.length() == 6) {
546 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 2), 16);
547 final int tmpGreen = Integer.parseInt(tmpHex.substring(2, 4), 16);
548 final int tmpBlue = Integer.parseInt(tmpHex.substring(4, 6), 16);
549 return new Color(tmpRed, tmpGreen, tmpBlue);
550 }
551
552 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 1) + tmpHex.substring(0, 1), 16);
553 final int tmpGreen = Integer.parseInt(tmpHex.substring(1, 2) + tmpHex.substring(1, 2), 16);
554 final int tmpBlue = Integer.parseInt(tmpHex.substring(2, 3) + tmpHex.substring(2, 3), 16);
555 return new Color(tmpRed, tmpGreen, tmpBlue);
556 }
557
558
559
560
561
562
563 public static Color findColorRGB(final String token) {
564 if (token == null) {
565 return null;
566 }
567 final Matcher tmpMatcher = RGB_COLOR.matcher(token);
568 if (!tmpMatcher.find()) {
569 return null;
570 }
571
572 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
573 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
574 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
575 return new Color(tmpRed, tmpGreen, tmpBlue);
576 }
577
578
579
580
581
582
583 public static Color findColorRGBA(final String token) {
584 if (token == null) {
585 return null;
586 }
587 final Matcher tmpMatcher = RGBA_COLOR.matcher(token);
588 if (!tmpMatcher.find()) {
589 return null;
590 }
591
592 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
593 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
594 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
595 final int tmpAlpha = (int) (Float.parseFloat(tmpMatcher.group(4)) * 255);
596 return new Color(tmpRed, tmpGreen, tmpBlue, tmpAlpha);
597 }
598
599
600
601
602
603
604 public static Color findColorHSL(final String token) {
605 if (token == null) {
606 return null;
607 }
608 final Matcher tmpMatcher = HSL_COLOR.matcher(token);
609 if (!tmpMatcher.find()) {
610 return null;
611 }
612
613 final float tmpHue = Float.parseFloat(tmpMatcher.group(1)) / 360f;
614 final float tmpSaturation = Float.parseFloat(tmpMatcher.group(4)) / 100f;
615 final float tmpLightness = Float.parseFloat(tmpMatcher.group(7)) / 100f;
616 return hslToRgb(tmpHue, tmpSaturation, tmpLightness);
617 }
618
619
620
621
622
623
624
625
626
627
628
629 private static Color hslToRgb(final float h, final float s, final float l) {
630 if (s == 0f) {
631 return new Color(to255(l), to255(l), to255(l));
632 }
633
634 final float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
635 final float p = 2 * l - q;
636 final float r = hueToRgb(p, q, h + 1f / 3f);
637 final float g = hueToRgb(p, q, h);
638 final float b = hueToRgb(p, q, h - 1f / 3f);
639
640 return new Color(to255(r), to255(g), to255(b));
641 }
642
643 private static float hueToRgb(final float p, final float q, float t) {
644 if (t < 0f) {
645 t += 1f;
646 }
647
648 if (t > 1f) {
649 t -= 1f;
650 }
651
652 if (t < 1f / 6f) {
653 return p + (q - p) * 6f * t;
654 }
655
656 if (t < 1f / 2f) {
657 return q;
658 }
659
660 if (t < 2f / 3f) {
661 return p + (q - p) * (2f / 3f - t) * 6f;
662 }
663
664 return p;
665 }
666
667 private static int to255(final float value) {
668 return (int) Math.min(255, 256 * value);
669 }
670
671
672
673
674
675
676
677 public static String formatColor(final Color color) {
678 return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
679 }
680
681
682
683
684
685
686
687
688
689 public static String sanitizeForAppendReplacement(final String toSanitize) {
690 return org.apache.commons.lang3.StringUtils.replaceEach(toSanitize,
691 new String[] {"\\", "$"}, new String[]{"\\\\", "\\$"});
692 }
693
694
695
696
697
698
699
700
701 public static String sanitizeForFileName(final String toSanitize) {
702 return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
703 }
704
705
706
707
708
709
710
711 public static String cssCamelize(final String string) {
712 if (string == null) {
713 return null;
714 }
715
716 String result = CAMELIZE_CACHE.get(string);
717 if (null != result) {
718 return result;
719 }
720
721
722 final int pos = string.indexOf('-');
723 if (pos == -1 || pos == string.length() - 1) {
724
725 CAMELIZE_CACHE.put(string, string);
726 return string;
727 }
728
729 final StringBuilder builder = new StringBuilder(string);
730 builder.deleteCharAt(pos);
731 builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
732
733 int i = pos + 1;
734 while (i < builder.length() - 1) {
735 if (builder.charAt(i) == '-') {
736 builder.deleteCharAt(i);
737 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
738 }
739 i++;
740 }
741 result = builder.toString();
742 CAMELIZE_CACHE.put(string, result);
743
744 return result;
745 }
746
747
748
749
750
751
752
753
754 public static String toRootLowerCase(final String s) {
755 return s == null ? null : s.toLowerCase(Locale.ROOT);
756 }
757
758
759
760
761
762
763
764
765 public static String cssDeCamelize(final String string) {
766 if (string == null || string.isEmpty()) {
767 return string;
768 }
769
770 final StringBuilder builder = new StringBuilder();
771 for (int i = 0; i < string.length(); i++) {
772 final char ch = string.charAt(i);
773 if (Character.isUpperCase(ch)) {
774 builder.append('-').append(Character.toLowerCase(ch));
775 }
776 else {
777 builder.append(ch);
778 }
779 }
780 return builder.toString();
781 }
782
783
784
785
786
787
788
789
790 public static byte[] toByteArray(final String content, final Charset charset) {
791 if (content == null || content.isEmpty()) {
792 return new byte[0];
793 }
794
795 return content.getBytes(charset);
796 }
797
798
799
800
801
802
803
804
805
806 public static String[] splitAtJavaWhitespace(final String str) {
807 final String[] parts = org.apache.commons.lang3.StringUtils.split(str);
808 if (parts == null) {
809 return new String[0];
810 }
811 return parts;
812 }
813
814
815
816
817
818
819
820
821 public static String[] splitAtBlank(final String str) {
822 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ' ');
823 if (parts == null) {
824 return new String[0];
825 }
826 return parts;
827 }
828
829
830
831
832
833
834
835
836 public static String[] splitAtComma(final String str) {
837 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ',');
838 if (parts == null) {
839 return new String[0];
840 }
841 return parts;
842 }
843
844
845
846
847
848
849
850
851 public static String[] splitAtCommaOrBlank(final String str) {
852 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ", ");
853 if (parts == null) {
854 return new String[0];
855 }
856 return parts;
857 }
858
859
860
861
862
863
864
865
866
867
868
869 public static String substringBefore(final String str, final String find) {
870 if (isEmptyOrNull(find)) {
871 throw new IllegalArgumentException("'find' string parameter has to be not empty and not null");
872 }
873
874 if (isEmptyString(str)) {
875 return str;
876 }
877
878 final int pos = str.indexOf(find);
879 if (pos == -1) {
880 return str;
881 }
882 return str.substring(0, pos);
883 }
884
885
886
887
888
889
890
891
892
893 public static int toInt(final String str, final int defaultValue) {
894 try {
895 return Integer.parseInt(str);
896 }
897 catch (final RuntimeException e) {
898 return defaultValue;
899 }
900 }
901
902
903
904
905
906
907
908
909
910 public static float toFloat(final String str, final float defaultValue) {
911 try {
912 return Float.parseFloat(str);
913 }
914 catch (final RuntimeException e) {
915 return defaultValue;
916 }
917 }
918
919
920
921
922
923
924
925
926
927
928 public static String trimRight(final String str) {
929 if (isEmptyOrNull(str)) {
930 return str;
931 }
932
933 int end = str.length();
934 while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
935 end--;
936 }
937
938 if (end == str.length()) {
939 return str;
940 }
941
942 return str.substring(0, end);
943 }
944
945
946
947
948
949
950 public static boolean containsOnly(final CharSequence cs, final char... valid) {
951 if (valid == null || valid.length == 0) {
952 throw new IllegalArgumentException("Expected valid char[] can't be null or empty");
953 }
954 if (isEmptyOrNull(cs)) {
955 return false;
956 }
957
958 final int csLength = cs.length();
959 final int validLength = valid.length;
960 for (int i = 0; i < csLength; i++) {
961 final char testChar = cs.charAt(i);
962 int j = 0;
963 for ( ; j < validLength; j++) {
964 final char validChar = valid[j];
965 if (validChar == testChar) {
966 break;
967 }
968 }
969 if (j == validLength) {
970 return false;
971 }
972 }
973
974 return true;
975 }
976 }