1 package be
.nikiroo
.utils
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.IOException
;
5 import java
.io
.UnsupportedEncodingException
;
6 import java
.security
.MessageDigest
;
7 import java
.security
.NoSuchAlgorithmException
;
8 import java
.text
.Normalizer
;
9 import java
.text
.Normalizer
.Form
;
10 import java
.text
.ParseException
;
11 import java
.text
.SimpleDateFormat
;
12 import java
.util
.AbstractMap
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Date
;
15 import java
.util
.List
;
16 import java
.util
.Map
.Entry
;
17 import java
.util
.Scanner
;
18 import java
.util
.regex
.Pattern
;
20 import org
.unbescape
.html
.HtmlEscape
;
21 import org
.unbescape
.html
.HtmlEscapeLevel
;
22 import org
.unbescape
.html
.HtmlEscapeType
;
25 * This class offer some utilities based around {@link String}s.
29 public class StringUtils
{
31 * This enum type will decide the alignment of a {@link String} when padding
32 * or justification is applied (if there is enough horizontal space for it
35 public enum Alignment
{
36 /** Aligned at left. */
40 /** Aligned at right. */
42 /** Full justified (to both left and right). */
45 // Old Deprecated values:
47 /** DEPRECATED: please use LEFT. */
50 /** DEPRECATED: please use CENTER. */
53 /** DEPRECATED: please use RIGHT. */
58 * Return the non-deprecated version of this enum if needed (or return
61 * @return the non-deprecated value
63 Alignment
undeprecate() {
64 if (this == Beginning
)
74 static private Pattern marks
= getMarks();
77 * Fix the size of the given {@link String} either with space-padding or by
81 * the {@link String} to fix
83 * the size of the resulting {@link String} or -1 for a noop
85 * @return the resulting {@link String} of size <i>size</i>
87 static public String
padString(String text
, int width
) {
88 return padString(text
, width
, true, null);
92 * Fix the size of the given {@link String} either with space-padding or by
93 * optionally shortening it.
96 * the {@link String} to fix
98 * the size of the resulting {@link String} if the text fits or
99 * if cut is TRUE or -1 for a noop
101 * cut the {@link String} shorter if needed
103 * align the {@link String} in this position if we have enough
104 * space (default is Alignment.Beginning)
106 * @return the resulting {@link String} of size <i>size</i> minimum
108 static public String
padString(String text
, int width
, boolean cut
,
112 align
= Alignment
.LEFT
;
115 align
= align
.undeprecate();
121 int diff
= width
- text
.length();
125 text
= text
.substring(0, width
);
126 } else if (diff
> 0) {
127 if (diff
< 2 && align
!= Alignment
.RIGHT
)
128 align
= Alignment
.LEFT
;
132 text
= new String(new char[diff
]).replace('\0', ' ') + text
;
135 int pad1
= (diff
) / 2;
136 int pad2
= (diff
+ 1) / 2;
137 text
= new String(new char[pad1
]).replace('\0', ' ') + text
138 + new String(new char[pad2
]).replace('\0', ' ');
142 text
= text
+ new String(new char[diff
]).replace('\0', ' ');
152 * Justify a text into width-sized (at the maximum) lines.
155 * the {@link String} to justify
157 * the maximum size of the resulting lines
159 * @return a list of justified text lines
161 static public List
<String
> justifyText(String text
, int width
) {
162 return justifyText(text
, width
, null);
166 * Justify a text into width-sized (at the maximum) lines.
169 * the {@link String} to justify
171 * the maximum size of the resulting lines
173 * align the lines in this position (default is
174 * Alignment.Beginning)
176 * @return a list of justified text lines
178 static public List
<String
> justifyText(String text
, int width
,
181 align
= Alignment
.LEFT
;
184 align
= align
.undeprecate();
188 return StringJustifier
.center(text
, width
);
190 return StringJustifier
.right(text
, width
);
192 return StringJustifier
.full(text
, width
);
195 return StringJustifier
.left(text
, width
);
200 * Justify a text into width-sized (at the maximum) lines.
203 * the {@link String} to justify
205 * the maximum size of the resulting lines
207 * @return a list of justified text lines
209 static public List
<String
> justifyText(List
<String
> text
, int width
) {
210 return justifyText(text
, width
, null);
214 * Justify a text into width-sized (at the maximum) lines.
217 * the {@link String} to justify
219 * the maximum size of the resulting lines
221 * align the lines in this position (default is
222 * Alignment.Beginning)
224 * @return a list of justified text lines
226 static public List
<String
> justifyText(List
<String
> text
, int width
,
228 List
<String
> result
= new ArrayList
<String
>();
230 // Content <-> Bullet spacing (null = no spacing)
231 List
<Entry
<String
, String
>> lines
= new ArrayList
<Entry
<String
, String
>>();
232 StringBuilder previous
= null;
233 StringBuilder tmp
= new StringBuilder();
234 String previousItemBulletSpacing
= null;
235 String itemBulletSpacing
= null;
236 for (String inputLine
: text
) {
237 boolean previousLineComplete
= true;
239 String current
= inputLine
.replace("\t", " ");
240 itemBulletSpacing
= getItemSpacing(current
);
241 boolean bullet
= isItemLine(current
);
242 if ((previousItemBulletSpacing
== null || itemBulletSpacing
243 .length() <= previousItemBulletSpacing
.length()) && !bullet
) {
244 itemBulletSpacing
= null;
247 if (itemBulletSpacing
!= null) {
248 current
= current
.trim();
249 if (!current
.isEmpty() && bullet
) {
250 current
= current
.substring(1);
252 current
= current
.trim();
253 previousLineComplete
= bullet
;
256 for (String word
: current
.split(" ")) {
257 if (word
.isEmpty()) {
261 if (tmp
.length() > 0) {
264 tmp
.append(word
.trim());
266 current
= tmp
.toString();
268 previousLineComplete
= current
.isEmpty()
269 || previousItemBulletSpacing
!= null
270 || (previous
!= null && isFullLine(previous
));
273 if (previous
== null) {
274 previous
= new StringBuilder();
276 if (previousLineComplete
) {
277 lines
.add(new AbstractMap
.SimpleEntry
<String
, String
>(
278 previous
.toString(), previousItemBulletSpacing
));
279 previous
.setLength(0);
280 previousItemBulletSpacing
= itemBulletSpacing
;
282 previous
.append(' ');
286 previous
.append(current
);
290 if (previous
!= null) {
291 lines
.add(new AbstractMap
.SimpleEntry
<String
, String
>(previous
292 .toString(), previousItemBulletSpacing
));
295 for (Entry
<String
, String
> line
: lines
) {
296 String content
= line
.getKey();
297 String spacing
= line
.getValue();
299 String bullet
= "- ";
300 if (spacing
== null) {
305 if (spacing
.length() > width
+ 3) {
309 for (String subline
: StringUtils
.justifyText(content
, width
310 - (spacing
.length() + bullet
.length()), align
)) {
311 result
.add(spacing
+ bullet
+ subline
);
312 if (!bullet
.isEmpty()) {
322 * Sanitise the given input to make it more Terminal-friendly by removing
323 * combining characters.
326 * the input to sanitise
327 * @param allowUnicode
328 * allow Unicode or only allow ASCII Latin characters
330 * @return the sanitised {@link String}
332 static public String
sanitize(String input
, boolean allowUnicode
) {
333 return sanitize(input
, allowUnicode
, !allowUnicode
);
337 * Sanitise the given input to make it more Terminal-friendly by removing
338 * combining characters.
341 * the input to sanitise
342 * @param allowUnicode
343 * allow Unicode or only allow ASCII Latin characters
344 * @param removeAllAccents
345 * TRUE to replace all accentuated characters by their non
346 * accentuated counter-parts
348 * @return the sanitised {@link String}
350 static public String
sanitize(String input
, boolean allowUnicode
,
351 boolean removeAllAccents
) {
353 if (removeAllAccents
) {
354 input
= Normalizer
.normalize(input
, Form
.NFKD
);
356 input
= marks
.matcher(input
).replaceAll("");
360 input
= Normalizer
.normalize(input
, Form
.NFKC
);
363 StringBuilder builder
= new StringBuilder();
364 for (int index
= 0; index
< input
.length(); index
++) {
365 char car
= input
.charAt(index
);
366 // displayable chars in ASCII are in the range 32<->255,
368 if (car
>= 32 && car
<= 255 && car
!= 127) {
372 input
= builder
.toString();
379 * Convert between the time in milliseconds to a {@link String} in a "fixed"
380 * way (to exchange data over the wire, for instance).
382 * Precise to the second.
385 * the specified number of milliseconds since the standard base
386 * time known as "the epoch", namely January 1, 1970, 00:00:00
389 * @return the time as a {@link String}
391 static public String
fromTime(long time
) {
392 SimpleDateFormat sdf
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
393 return sdf
.format(new Date(time
));
397 * Convert between the time as a {@link String} to milliseconds in a "fixed"
398 * way (to exchange data over the wire, for instance).
400 * Precise to the second.
403 * the time as a {@link String}
405 * @return the number of milliseconds since the standard base time known as
406 * "the epoch", namely January 1, 1970, 00:00:00 GMT, or -1 in case
409 * @throws ParseException
410 * in case of parse error
412 static public long toTime(String displayTime
) throws ParseException
{
413 SimpleDateFormat sdf
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
414 return sdf
.parse(displayTime
).getTime();
418 * Return a hash of the given {@link String}.
425 static public String
getMd5Hash(String input
) {
427 MessageDigest md
= MessageDigest
.getInstance("MD5");
428 md
.update(input
.getBytes("UTF-8"));
429 byte byteData
[] = md
.digest();
431 StringBuffer hexString
= new StringBuffer();
432 for (int i
= 0; i
< byteData
.length
; i
++) {
433 String hex
= Integer
.toHexString(0xff & byteData
[i
]);
434 if (hex
.length() == 1)
435 hexString
.append('0');
436 hexString
.append(hex
);
439 return hexString
.toString();
440 } catch (NoSuchAlgorithmException e
) {
442 } catch (UnsupportedEncodingException e
) {
448 * Remove the HTML content from the given input, and un-html-ize the rest.
451 * the HTML-encoded content
453 * @return the HTML-free equivalent content
455 public static String
unhtml(String html
) {
456 StringBuilder builder
= new StringBuilder();
459 for (char car
: html
.toCharArray()) {
462 } else if (car
== '>') {
464 } else if (inTag
<= 0) {
469 char nbsp
= ' '; // non-breakable space (a special char)
471 return HtmlEscape
.unescapeHtml(builder
.toString()).replace(nbsp
, space
);
475 * Escape the given {@link String} so it can be used in XML, as content.
478 * the input {@link String}
480 * @return the escaped {@link String}
482 public static String
xmlEscape(String input
) {
487 return HtmlEscape
.escapeHtml(input
,
488 HtmlEscapeType
.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA
,
489 HtmlEscapeLevel
.LEVEL_1_ONLY_MARKUP_SIGNIFICANT
);
493 * Escape the given {@link String} so it can be used in XML, as text content
494 * inside double-quotes.
497 * the input {@link String}
499 * @return the escaped {@link String}
501 public static String
xmlEscapeQuote(String input
) {
506 return HtmlEscape
.escapeHtml(input
,
507 HtmlEscapeType
.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA
,
508 HtmlEscapeLevel
.LEVEL_1_ONLY_MARKUP_SIGNIFICANT
);
512 * Zip the data and then encode it into Base64.
517 * @return the Base64 zipped version
519 public static String
zip64(String data
) {
521 return Base64
.encodeBytes(data
.getBytes(), Base64
.GZIP
);
522 } catch (IOException e
) {
529 * Unconvert from Base64 then unzip the content.
532 * the data in Base64 format
534 * @return the raw data
536 * @throws IOException
537 * in case of I/O error
539 public static String
unzip64(String data
) throws IOException
{
540 ByteArrayInputStream in
= new ByteArrayInputStream(Base64
.decode(data
,
543 Scanner scan
= new Scanner(in
);
544 scan
.useDelimiter("\\A");
553 * The "remove accents" pattern.
555 * @return the pattern, or NULL if a problem happens
557 private static Pattern
getMarks() {
560 .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
561 } catch (Exception e
) {
562 // Can fail on Android...
567 // justify List<String> related:
569 static private boolean isFullLine(StringBuilder line
) {
570 return line
.length() == 0 //
571 || line
.charAt(line
.length() - 1) == '.'
572 || line
.charAt(line
.length() - 1) == '"'
573 || line
.charAt(line
.length() - 1) == '»';
576 static private boolean isItemLine(String line
) {
577 String spacing
= getItemSpacing(line
);
578 return spacing
!= null && line
.charAt(spacing
.length()) == '-';
581 static private String
getItemSpacing(String line
) {
583 for (i
= 0; i
< line
.length(); i
++) {
584 if (line
.charAt(i
) != ' ') {
585 return line
.substring(0, i
);