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