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 public static boolean equalsChar(final char expected, final CharSequence s) {
79 return s != null && s.length() == 1 && expected == s.charAt(0);
80 }
81
82
83
84
85
86
87 public static boolean startsWithIgnoreCase(final String s, final String expectedStart) {
88 if (expectedStart == null || expectedStart.length() == 0) {
89 throw new IllegalArgumentException("Expected start string can't be null or empty");
90 }
91
92 if (s == null) {
93 return false;
94 }
95 if (s == expectedStart) {
96 return true;
97 }
98
99 return s.regionMatches(true, 0, expectedStart, 0, expectedStart.length());
100 }
101
102
103
104
105
106
107
108 public static String escapeXmlChars(final String s) {
109 return org.apache.commons.lang3.StringUtils.
110 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&", "<", ">"});
111 }
112
113
114
115
116
117
118
119 public static String escapeXml(final String text) {
120 if (text == null) {
121 return null;
122 }
123
124 StringBuilder escaped = null;
125
126 final int offset = 0;
127 final int max = text.length();
128
129 int readOffset = offset;
130
131 for (int i = offset; i < max; i++) {
132 final int codepoint = Character.codePointAt(text, i);
133 final boolean codepointValid = supportedByXML10(codepoint);
134
135 if (!codepointValid
136 || codepoint == '<'
137 || codepoint == '>'
138 || codepoint == '&'
139 || codepoint == '\''
140 || codepoint == '"') {
141
142
143 if (escaped == null) {
144 escaped = new StringBuilder(max);
145 }
146
147 if (i > readOffset) {
148 escaped.append(text, readOffset, i);
149 }
150
151 if (Character.charCount(codepoint) > 1) {
152 i++;
153 }
154 readOffset = i + 1;
155
156
157 if (!codepointValid) {
158 continue;
159 }
160
161 if (codepoint == '<') {
162 escaped.append("<");
163 }
164 else if (codepoint == '>') {
165 escaped.append(">");
166 }
167 else if (codepoint == '&') {
168 escaped.append("&");
169 }
170 else if (codepoint == '\'') {
171 escaped.append("'");
172 }
173 else if (codepoint == '\"') {
174 escaped.append(""");
175 }
176 }
177 }
178
179 if (escaped == null) {
180 return text;
181 }
182
183 if (max > readOffset) {
184 escaped.append(text, readOffset, max);
185 }
186
187 return escaped.toString();
188 }
189
190
191
192
193
194
195
196
197 public static String escapeXmlAttributeValue(final String attValue) {
198 if (attValue == null) {
199 return null;
200 }
201
202 StringBuilder escaped = null;
203
204 final int offset = 0;
205 final int max = attValue.length();
206
207 int readOffset = offset;
208
209 for (int i = offset; i < max; i++) {
210 final int codepoint = Character.codePointAt(attValue, i);
211 final boolean codepointValid = supportedByXML10(codepoint);
212
213 if (!codepointValid
214 || codepoint == '<'
215 || codepoint == '&'
216 || codepoint == '"') {
217
218
219 if (escaped == null) {
220 escaped = new StringBuilder(max);
221 }
222
223 if (i > readOffset) {
224 escaped.append(attValue, readOffset, i);
225 }
226
227 if (Character.charCount(codepoint) > 1) {
228 i++;
229 }
230 readOffset = i + 1;
231
232
233 if (!codepointValid) {
234 continue;
235 }
236
237 if (codepoint == '<') {
238 escaped.append("<");
239 }
240 else if (codepoint == '&') {
241 escaped.append("&");
242 }
243 else if (codepoint == '\"') {
244 escaped.append(""");
245 }
246 }
247 }
248
249 if (escaped == null) {
250 return attValue;
251 }
252
253 if (max > readOffset) {
254 escaped.append(attValue, readOffset, max);
255 }
256
257 return escaped.toString();
258 }
259
260
261
262
263
264
265 private static boolean supportedByXML10(final int codepoint) {
266 if (codepoint < 0x20) {
267 return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD;
268 }
269 if (codepoint <= 0xD7FF) {
270 return true;
271 }
272
273 if (codepoint < 0xE000) {
274 return false;
275 }
276 if (codepoint <= 0xFFFD) {
277 return true;
278 }
279
280 if (codepoint < 0x10000) {
281 return false;
282 }
283 if (codepoint <= 0x10FFFF) {
284 return true;
285 }
286
287 return true;
288 }
289
290
291
292
293
294
295
296
297
298
299
300 public static int indexOf(final String s, final char searchChar, final int beginIndex, final int endIndex) {
301 for (int i = beginIndex; i < endIndex; i++) {
302 if (s.charAt(i) == searchChar) {
303 return i;
304 }
305 }
306 return -1;
307 }
308
309
310
311
312
313
314 public static Color asColorHexadecimal(final String token) {
315 if (token == null) {
316 return null;
317 }
318 final Matcher tmpMatcher = HEX_COLOR.matcher(token);
319 final boolean tmpFound = tmpMatcher.matches();
320 if (!tmpFound) {
321 return null;
322 }
323
324 final String tmpHex = tmpMatcher.group(1);
325 if (tmpHex.length() == 6) {
326 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 2), 16);
327 final int tmpGreen = Integer.parseInt(tmpHex.substring(2, 4), 16);
328 final int tmpBlue = Integer.parseInt(tmpHex.substring(4, 6), 16);
329 return new Color(tmpRed, tmpGreen, tmpBlue);
330 }
331
332 final int tmpRed = Integer.parseInt(tmpHex.substring(0, 1) + tmpHex.substring(0, 1), 16);
333 final int tmpGreen = Integer.parseInt(tmpHex.substring(1, 2) + tmpHex.substring(1, 2), 16);
334 final int tmpBlue = Integer.parseInt(tmpHex.substring(2, 3) + tmpHex.substring(2, 3), 16);
335 return new Color(tmpRed, tmpGreen, tmpBlue);
336 }
337
338
339
340
341
342
343 public static Color findColorRGB(final String token) {
344 if (token == null) {
345 return null;
346 }
347 final Matcher tmpMatcher = RGB_COLOR.matcher(token);
348 if (!tmpMatcher.find()) {
349 return null;
350 }
351
352 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
353 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
354 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
355 return new Color(tmpRed, tmpGreen, tmpBlue);
356 }
357
358
359
360
361
362
363 public static Color findColorRGBA(final String token) {
364 if (token == null) {
365 return null;
366 }
367 final Matcher tmpMatcher = RGBA_COLOR.matcher(token);
368 if (!tmpMatcher.find()) {
369 return null;
370 }
371
372 final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
373 final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
374 final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
375 final int tmpAlpha = (int) (Float.parseFloat(tmpMatcher.group(4)) * 255);
376 return new Color(tmpRed, tmpGreen, tmpBlue, tmpAlpha);
377 }
378
379
380
381
382
383
384 public static Color findColorHSL(final String token) {
385 if (token == null) {
386 return null;
387 }
388 final Matcher tmpMatcher = HSL_COLOR.matcher(token);
389 if (!tmpMatcher.find()) {
390 return null;
391 }
392
393 final float tmpHue = Float.parseFloat(tmpMatcher.group(1)) / 360f;
394 final float tmpSaturation = Float.parseFloat(tmpMatcher.group(4)) / 100f;
395 final float tmpLightness = Float.parseFloat(tmpMatcher.group(7)) / 100f;
396 return hslToRgb(tmpHue, tmpSaturation, tmpLightness);
397 }
398
399
400
401
402
403
404
405
406
407
408
409 private static Color hslToRgb(final float h, final float s, final float l) {
410 if (s == 0f) {
411 return new Color(to255(l), to255(l), to255(l));
412 }
413
414 final float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
415 final float p = 2 * l - q;
416 final float r = hueToRgb(p, q, h + 1f / 3f);
417 final float g = hueToRgb(p, q, h);
418 final float b = hueToRgb(p, q, h - 1f / 3f);
419
420 return new Color(to255(r), to255(g), to255(b));
421 }
422
423 private static float hueToRgb(final float p, final float q, float t) {
424 if (t < 0f) {
425 t += 1f;
426 }
427
428 if (t > 1f) {
429 t -= 1f;
430 }
431
432 if (t < 1f / 6f) {
433 return p + (q - p) * 6f * t;
434 }
435
436 if (t < 1f / 2f) {
437 return q;
438 }
439
440 if (t < 2f / 3f) {
441 return p + (q - p) * (2f / 3f - t) * 6f;
442 }
443
444 return p;
445 }
446
447 private static int to255(final float value) {
448 return (int) Math.min(255, 256 * value);
449 }
450
451
452
453
454
455
456
457 public static String formatColor(final Color color) {
458 return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
459 }
460
461
462
463
464
465
466
467
468
469 public static String sanitizeForAppendReplacement(final String toSanitize) {
470 return org.apache.commons.lang3.StringUtils.replaceEach(toSanitize,
471 new String[] {"\\", "$"}, new String[]{"\\\\", "\\$"});
472 }
473
474
475
476
477
478
479
480
481 public static String sanitizeForFileName(final String toSanitize) {
482 return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
483 }
484
485
486
487
488
489
490
491 public static String cssCamelize(final String string) {
492 if (string == null) {
493 return null;
494 }
495
496 String result = CAMELIZE_CACHE.get(string);
497 if (null != result) {
498 return result;
499 }
500
501
502 final int pos = string.indexOf('-');
503 if (pos == -1 || pos == string.length() - 1) {
504
505 CAMELIZE_CACHE.put(string, string);
506 return string;
507 }
508
509 final StringBuilder builder = new StringBuilder(string);
510 builder.deleteCharAt(pos);
511 builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
512
513 int i = pos + 1;
514 while (i < builder.length() - 1) {
515 if (builder.charAt(i) == '-') {
516 builder.deleteCharAt(i);
517 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
518 }
519 i++;
520 }
521 result = builder.toString();
522 CAMELIZE_CACHE.put(string, result);
523
524 return result;
525 }
526
527
528
529
530
531
532
533
534 public static String toRootLowerCase(final String s) {
535 return s == null ? null : s.toLowerCase(Locale.ROOT);
536 }
537
538
539
540
541
542
543
544
545 public static String cssDeCamelize(final String string) {
546 if (string == null || string.isEmpty()) {
547 return string;
548 }
549
550 final StringBuilder builder = new StringBuilder();
551 for (int i = 0; i < string.length(); i++) {
552 final char ch = string.charAt(i);
553 if (Character.isUpperCase(ch)) {
554 builder.append('-').append(Character.toLowerCase(ch));
555 }
556 else {
557 builder.append(ch);
558 }
559 }
560 return builder.toString();
561 }
562
563
564
565
566
567
568
569
570 public static byte[] toByteArray(final String content, final Charset charset) {
571 if (content == null || content.isEmpty()) {
572 return new byte[0];
573 }
574
575 return content.getBytes(charset);
576 }
577
578
579
580
581
582
583
584
585
586 public static String[] splitAtJavaWhitespace(final String str) {
587 final String[] parts = org.apache.commons.lang3.StringUtils.split(str);
588 if (parts == null) {
589 return new String[0];
590 }
591 return parts;
592 }
593
594
595
596
597
598
599
600
601 public static String[] splitAtBlank(final String str) {
602 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ' ');
603 if (parts == null) {
604 return new String[0];
605 }
606 return parts;
607 }
608
609
610
611
612
613
614
615
616 public static String[] splitAtComma(final String str) {
617 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ',');
618 if (parts == null) {
619 return new String[0];
620 }
621 return parts;
622 }
623
624
625
626
627
628
629
630
631 public static String[] splitAtCommaOrBlank(final String str) {
632 final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ", ");
633 if (parts == null) {
634 return new String[0];
635 }
636 return parts;
637 }
638 }