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 public static String escapeXmlChars(final String s) {
242 return org.apache.commons.lang3.StringUtils.
243 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&", "<", ">"});
244 }
245
246
247
248
249
250
251
252 public static String escapeXml(final String text) {
253 if (text == null) {
254 return null;
255 }
256
257 StringBuilder escaped = null;
258
259 final int offset = 0;
260 final int max = text.length();
261
262 int readOffset = offset;
263
264 for (int i = offset; i < max; i++) {
265 final int codepoint = Character.codePointAt(text, i);
266 final boolean codepointValid = supportedByXML10(codepoint);
267
268 if (!codepointValid
269 || codepoint == '<'
270 || codepoint == '>'
271 || codepoint == '&'
272 || codepoint == '\''
273 || codepoint == '"') {
274
275
276 if (escaped == null) {
277 escaped = new StringBuilder(max);
278 }
279
280 if (i > readOffset) {
281 escaped.append(text, readOffset, i);
282 }
283
284 if (Character.charCount(codepoint) > 1) {
285 i++;
286 }
287 readOffset = i + 1;
288
289
290 if (!codepointValid) {
291 continue;
292 }
293
294 if (codepoint == '<') {
295 escaped.append("<");
296 }
297 else if (codepoint == '>') {
298 escaped.append(">");
299 }
300 else if (codepoint == '&') {
301 escaped.append("&");
302 }
303 else if (codepoint == '\'') {
304 escaped.append("'");
305 }
306 else if (codepoint == '\"') {
307 escaped.append(""");
308 }
309 }
310 }
311
312 if (escaped == null) {
313 return text;
314 }
315
316 if (max > readOffset) {
317 escaped.append(text, readOffset, max);
318 }
319
320 return escaped.toString();
321 }
322
323
324
325
326
327
328
329
330 public static String escapeXmlAttributeValue(final String attValue) {
331 if (attValue == null) {
332 return null;
333 }
334
335 StringBuilder escaped = null;
336
337 final int offset = 0;
338 final int max = attValue.length();
339
340 int readOffset = offset;
341
342 for (int i = offset; i < max; i++) {
343 final int codepoint = Character.codePointAt(attValue, i);
344 final boolean codepointValid = supportedByXML10(codepoint);
345
346 if (!codepointValid
347 || codepoint == '<'
348 || codepoint == '&'
349 || codepoint == '"') {
350
351
352 if (escaped == null) {
353 escaped = new StringBuilder(max);
354 }
355
356 if (i > readOffset) {
357 escaped.append(attValue, readOffset, i);
358 }
359
360 if (Character.charCount(codepoint) > 1) {
361 i++;
362 }
363 readOffset = i + 1;
364
365
366 if (!codepointValid) {
367 continue;
368 }
369
370 if (codepoint == '<') {
371 escaped.append("<");
372 }
373 else if (codepoint == '&') {
374 escaped.append("&");
375 }
376 else if (codepoint == '\"') {
377 escaped.append(""");
378 }
379 }
380 }
381
382 if (escaped == null) {
383 return attValue;
384 }
385
386 if (max > readOffset) {
387 escaped.append(attValue, readOffset, max);
388 }
389
390 return escaped.toString();
391 }
392
393
394
395
396
397
398 private static boolean supportedByXML10(final int codepoint) {
399 if (codepoint < 0x20) {
400 return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD;
401 }
402 if (codepoint <= 0xD7FF) {
403 return true;
404 }
405
406 if (codepoint < 0xE000) {
407 return false;
408 }
409 if (codepoint <= 0xFFFD) {
410 return true;
411 }
412
413 if (codepoint < 0x10000) {
414 return false;
415 }
416 if (codepoint <= 0x10FFFF) {
417 return true;
418 }
419
420 return true;
421 }
422
423
424
425
426
427
428
429
430
431
432
433 public static int indexOf(final String s, final char searchChar, final int beginIndex, final int endIndex) {
434 for (int i = beginIndex; i < endIndex; i++) {
435 if (s.charAt(i) == searchChar) {
436 return i;
437 }
438 }
439 return -1;
440 }
441
442
443
444
445
446
447 public static Color asColorHexadecimal(final String token) {
448 if (token == null) {
449 return null;
450 }
451 final Matcher tmpMatcher = HEX_COLOR.matcher(token);
452 final boolean tmpFound = tmpMatcher.matches();
453 if (!tmpFound) {
454 return null;
455 }
456
457 final String tmpHex = tmpMatcher.group(1);
458 if (tmpHex.length() == 6) {
459 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 2), 16);
460 final int tmpGreen = Integer.parseInt(tmpHex.substring(2, 4), 16);
461 final int tmpBlue = Integer.parseInt(tmpHex.substring(4, 6), 16);
462 return new Color(tmpRed, tmpGreen, tmpBlue);
463 }
464
465 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 1) + tmpHex.substring(0, 1), 16);
466 final int tmpGreen = Integer.parseInt(tmpHex.substring(1, 2) + tmpHex.substring(1, 2), 16);
467 final int tmpBlue = Integer.parseInt(tmpHex.substring(2, 3) + tmpHex.substring(2, 3), 16);
468 return new Color(tmpRed, tmpGreen, tmpBlue);
469 }
470
471
472
473
474
475
476 public static Color findColorRGB(final String token) {
477 if (token == null) {
478 return null;
479 }
480 final Matcher tmpMatcher = RGB_COLOR.matcher(token);
481 if (!tmpMatcher.find()) {
482 return null;
483 }
484
485 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
486 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
487 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
488 return new Color(tmpRed, tmpGreen, tmpBlue);
489 }
490
491
492
493
494
495
496 public static Color findColorRGBA(final String token) {
497 if (token == null) {
498 return null;
499 }
500 final Matcher tmpMatcher = RGBA_COLOR.matcher(token);
501 if (!tmpMatcher.find()) {
502 return null;
503 }
504
505 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
506 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
507 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
508 final int tmpAlpha = (int) (Float.parseFloat(tmpMatcher.group(4)) * 255);
509 return new Color(tmpRed, tmpGreen, tmpBlue, tmpAlpha);
510 }
511
512
513
514
515
516
517 public static Color findColorHSL(final String token) {
518 if (token == null) {
519 return null;
520 }
521 final Matcher tmpMatcher = HSL_COLOR.matcher(token);
522 if (!tmpMatcher.find()) {
523 return null;
524 }
525
526 final float tmpHue = Float.parseFloat(tmpMatcher.group(1)) / 360f;
527 final float tmpSaturation = Float.parseFloat(tmpMatcher.group(4)) / 100f;
528 final float tmpLightness = Float.parseFloat(tmpMatcher.group(7)) / 100f;
529 return hslToRgb(tmpHue, tmpSaturation, tmpLightness);
530 }
531
532
533
534
535
536
537
538
539
540
541
542 private static Color hslToRgb(final float h, final float s, final float l) {
543 if (s == 0f) {
544 return new Color(to255(l), to255(l), to255(l));
545 }
546
547 final float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
548 final float p = 2 * l - q;
549 final float r = hueToRgb(p, q, h + 1f / 3f);
550 final float g = hueToRgb(p, q, h);
551 final float b = hueToRgb(p, q, h - 1f / 3f);
552
553 return new Color(to255(r), to255(g), to255(b));
554 }
555
556 private static float hueToRgb(final float p, final float q, float t) {
557 if (t < 0f) {
558 t += 1f;
559 }
560
561 if (t > 1f) {
562 t -= 1f;
563 }
564
565 if (t < 1f / 6f) {
566 return p + (q - p) * 6f * t;
567 }
568
569 if (t < 1f / 2f) {
570 return q;
571 }
572
573 if (t < 2f / 3f) {
574 return p + (q - p) * (2f / 3f - t) * 6f;
575 }
576
577 return p;
578 }
579
580 private static int to255(final float value) {
581 return (int) Math.min(255, 256 * value);
582 }
583
584
585
586
587
588
589
590 public static String formatColor(final Color color) {
591 return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
592 }
593
594
595
596
597
598
599
600
601
602 public static String sanitizeForAppendReplacement(final String toSanitize) {
603 return org.apache.commons.lang3.StringUtils.replaceEach(toSanitize,
604 new String[] {"\\", "$"}, new String[]{"\\\\", "\\$"});
605 }
606
607
608
609
610
611
612
613
614 public static String sanitizeForFileName(final String toSanitize) {
615 return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
616 }
617
618
619
620
621
622
623
624 public static String cssCamelize(final String string) {
625 if (string == null) {
626 return null;
627 }
628
629 String result = CAMELIZE_CACHE.get(string);
630 if (null != result) {
631 return result;
632 }
633
634
635 final int pos = string.indexOf('-');
636 if (pos == -1 || pos == string.length() - 1) {
637
638 CAMELIZE_CACHE.put(string, string);
639 return string;
640 }
641
642 final StringBuilder builder = new StringBuilder(string);
643 builder.deleteCharAt(pos);
644 builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
645
646 int i = pos + 1;
647 while (i < builder.length() - 1) {
648 if (builder.charAt(i) == '-') {
649 builder.deleteCharAt(i);
650 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
651 }
652 i++;
653 }
654 result = builder.toString();
655 CAMELIZE_CACHE.put(string, result);
656
657 return result;
658 }
659
660
661
662
663
664
665
666
667 public static String toRootLowerCase(final String s) {
668 return s == null ? null : s.toLowerCase(Locale.ROOT);
669 }
670
671
672
673
674
675
676
677
678 public static String cssDeCamelize(final String string) {
679 if (string == null || string.isEmpty()) {
680 return string;
681 }
682
683 final StringBuilder builder = new StringBuilder();
684 for (int i = 0; i < string.length(); i++) {
685 final char ch = string.charAt(i);
686 if (Character.isUpperCase(ch)) {
687 builder.append('-').append(Character.toLowerCase(ch));
688 }
689 else {
690 builder.append(ch);
691 }
692 }
693 return builder.toString();
694 }
695
696
697
698
699
700
701
702
703 public static byte[] toByteArray(final String content, final Charset charset) {
704 if (content == null || content.isEmpty()) {
705 return new byte[0];
706 }
707
708 return content.getBytes(charset);
709 }
710
711
712
713
714
715
716
717
718
719 public static String[] splitAtJavaWhitespace(final String str) {
720 final String[] parts = org.apache.commons.lang3.StringUtils.split(str);
721 if (parts == null) {
722 return new String[0];
723 }
724 return parts;
725 }
726
727
728
729
730
731
732
733
734 public static String[] splitAtBlank(final String str) {
735 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ' ');
736 if (parts == null) {
737 return new String[0];
738 }
739 return parts;
740 }
741
742
743
744
745
746
747
748
749 public static String[] splitAtComma(final String str) {
750 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ',');
751 if (parts == null) {
752 return new String[0];
753 }
754 return parts;
755 }
756
757
758
759
760
761
762
763
764 public static String[] splitAtCommaOrBlank(final String str) {
765 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ", ");
766 if (parts == null) {
767 return new String[0];
768 }
769 return parts;
770 }
771 }