11
11
import java .util .function .BinaryOperator ;
12
12
import java .util .function .Function ;
13
13
import java .util .function .Supplier ;
14
+ import java .util .regex .Matcher ;
15
+ import java .util .regex .Pattern ;
14
16
import java .util .stream .Collector ;
15
- import java .util .stream .Collectors ;
16
17
17
18
/**
18
19
* Holds common, generic string utiltity methods.
24
25
* </p>
25
26
*/
26
27
public class StringUtil {
28
+ final static Set <String > lowercaseWords = Set .of (
29
+ "a" , "an" , "the" , "at" , "by" , "for" , "in" , "of" , "on" , "to" , "with" ,
30
+ "but" , "nor" , "so" , "yet" , "and" , "or" );
31
+
32
+ final static Set <String > conjunctionArticle = Set .of (
33
+ "a" , "an" , "the" , "and" , "or" );
27
34
28
35
/**
29
36
* Return {@code formatString} formatted with {@code o} as the first parameter.
@@ -44,7 +51,24 @@ public static String valueOrDefault(String[] parts, int index, String fallback)
44
51
}
45
52
46
53
public static String uppercaseFirst (String value ) {
47
- return value == null || value .isEmpty () ? value : Character .toUpperCase (value .charAt (0 )) + value .substring (1 );
54
+ if (value == null || value .isEmpty ()) {
55
+ return value ;
56
+ }
57
+ Pattern pattern = Pattern .compile ("^(\\ P{L}*)(.*)" );
58
+ Matcher matcher = pattern .matcher (value );
59
+
60
+ if (matcher .matches ()) {
61
+ String prefix = matcher .group (1 ); // Leading non-letters
62
+ String rest = matcher .group (2 ); // Everything else
63
+
64
+ if (rest .isEmpty ()) {
65
+ return value ; // No letters found
66
+ }
67
+
68
+ return prefix + Character .toUpperCase (rest .charAt (0 )) + rest .substring (1 );
69
+ }
70
+
71
+ return value ;
48
72
}
49
73
50
74
public static boolean equal (Object o1 , Object o2 ) {
@@ -196,14 +220,43 @@ public static String joinConjunct(List<String> list, String joiner, String lastJ
196
220
197
221
/** Return the given text converted to title case, with the first letter of each word capitalized. */
198
222
public static String toTitleCase (String text ) {
223
+ return toTitleCase (text , false );
224
+ }
225
+
226
+ /**
227
+ * Return the given text converted to title case, with the first letter of each word capitalized.
228
+ *
229
+ * @param text The text to convert to title case
230
+ * @param midClause If true, then don't capitalize conjunctions and articles in the middle of a clause.
231
+ */
232
+ public static String toTitleCase (String text , boolean midClause ) {
199
233
if (text == null || text .isEmpty ()) {
200
234
return text ;
201
235
}
202
- return Arrays .stream (text .split (" " ))
203
- .map (word -> word .isEmpty ()
204
- ? word
205
- : Character .toTitleCase (word .charAt (0 )) + word .substring (1 ).toLowerCase ())
206
- .collect (Collectors .joining (" " ));
236
+ String [] words = text .split (" " , -1 );
237
+ for (int i = 0 ; i < words .length ; i ++) {
238
+ String word = words [i ];
239
+ if (word .isEmpty () || word .matches ("\\ P{L}+" )) {
240
+ continue ; // Skip empty words
241
+ }
242
+ var lowerWord = word .toLowerCase ();
243
+
244
+ if (midClause && conjunctionArticle .contains (lowerWord )) {
245
+ // Don't capitalize conjunctions
246
+ words [i ] = lowerWord ;
247
+ } else if (i == 0 || i == words .length - 1 || !lowercaseWords .contains (lowerWord )) {
248
+ // // Capitalize; Handle markdown link display text: [word] -> [Word]
249
+ // if (word.startsWith("[") && word.length() > 1) {
250
+ // words[i] = "[" + Character.toTitleCase(word.charAt(1)) + word.substring(2).toLowerCase();
251
+ // } else {
252
+ // words[i] = Character.toTitleCase(word.charAt(0)) + word.substring(1).toLowerCase();
253
+ // }
254
+ words [i ] = uppercaseFirst (word );
255
+ } else {
256
+ words [i ] = lowerWord ;
257
+ }
258
+ }
259
+ return String .join (" " , words );
207
260
}
208
261
209
262
/** Returns true if the given string is non-null and non-blank. */
0 commit comments