Commit | Line | Data |
---|---|---|
ec1f3444 NR |
1 | package be.nikiroo.utils; |
2 | ||
ec1f3444 | 3 | import java.io.ByteArrayInputStream; |
ec1f3444 | 4 | import java.io.IOException; |
a359464f NR |
5 | import java.io.InputStream; |
6 | import java.io.OutputStream; | |
3f8349b7 | 7 | import java.io.UnsupportedEncodingException; |
ec1f3444 NR |
8 | import java.security.MessageDigest; |
9 | import java.security.NoSuchAlgorithmException; | |
10 | import java.text.Normalizer; | |
11 | import java.text.Normalizer.Form; | |
12 | import java.text.ParseException; | |
13 | import java.text.SimpleDateFormat; | |
dc22eb95 NR |
14 | import java.util.AbstractMap; |
15 | import java.util.ArrayList; | |
ec1f3444 | 16 | import java.util.Date; |
cc3e7291 | 17 | import java.util.List; |
dc22eb95 | 18 | import java.util.Map.Entry; |
db31c358 | 19 | import java.util.Scanner; |
ec1f3444 NR |
20 | import java.util.regex.Pattern; |
21 | ||
ec1f3444 NR |
22 | import org.unbescape.html.HtmlEscape; |
23 | import org.unbescape.html.HtmlEscapeLevel; | |
24 | import org.unbescape.html.HtmlEscapeType; | |
25 | ||
26 | /** | |
27 | * This class offer some utilities based around {@link String}s. | |
28 | * | |
29 | * @author niki | |
30 | */ | |
31 | public class StringUtils { | |
32 | /** | |
33 | * This enum type will decide the alignment of a {@link String} when padding | |
cc3e7291 NR |
34 | * or justification is applied (if there is enough horizontal space for it |
35 | * to be aligned). | |
ec1f3444 NR |
36 | */ |
37 | public enum Alignment { | |
38 | /** Aligned at left. */ | |
cc3e7291 | 39 | LEFT, |
ec1f3444 | 40 | /** Centered. */ |
cc3e7291 | 41 | CENTER, |
ec1f3444 | 42 | /** Aligned at right. */ |
cc3e7291 NR |
43 | RIGHT, |
44 | /** Full justified (to both left and right). */ | |
45 | JUSTIFY, | |
46 | ||
47 | // Old Deprecated values: | |
48 | ||
49 | /** DEPRECATED: please use LEFT. */ | |
50 | @Deprecated | |
51 | Beginning, | |
52 | /** DEPRECATED: please use CENTER. */ | |
53 | @Deprecated | |
54 | Center, | |
55 | /** DEPRECATED: please use RIGHT. */ | |
56 | @Deprecated | |
57 | End; | |
58 | ||
59 | /** | |
60 | * Return the non-deprecated version of this enum if needed (or return | |
61 | * self if not). | |
62 | * | |
63 | * @return the non-deprecated value | |
64 | */ | |
65 | Alignment undeprecate() { | |
66 | if (this == Beginning) | |
67 | return LEFT; | |
68 | if (this == Center) | |
69 | return CENTER; | |
70 | if (this == End) | |
71 | return RIGHT; | |
72 | return this; | |
73 | } | |
ec1f3444 NR |
74 | } |
75 | ||
e8aa5bf9 | 76 | static private Pattern marks = getMarks(); |
ec1f3444 NR |
77 | |
78 | /** | |
79 | * Fix the size of the given {@link String} either with space-padding or by | |
80 | * shortening it. | |
81 | * | |
82 | * @param text | |
83 | * the {@link String} to fix | |
84 | * @param width | |
85 | * the size of the resulting {@link String} or -1 for a noop | |
86 | * | |
87 | * @return the resulting {@link String} of size <i>size</i> | |
88 | */ | |
89 | static public String padString(String text, int width) { | |
451f434b | 90 | return padString(text, width, true, null); |
ec1f3444 NR |
91 | } |
92 | ||
93 | /** | |
94 | * Fix the size of the given {@link String} either with space-padding or by | |
95 | * optionally shortening it. | |
96 | * | |
97 | * @param text | |
98 | * the {@link String} to fix | |
99 | * @param width | |
100 | * the size of the resulting {@link String} if the text fits or | |
101 | * if cut is TRUE or -1 for a noop | |
102 | * @param cut | |
103 | * cut the {@link String} shorter if needed | |
104 | * @param align | |
105 | * align the {@link String} in this position if we have enough | |
451f434b | 106 | * space (default is Alignment.Beginning) |
ec1f3444 NR |
107 | * |
108 | * @return the resulting {@link String} of size <i>size</i> minimum | |
109 | */ | |
110 | static public String padString(String text, int width, boolean cut, | |
111 | Alignment align) { | |
112 | ||
451f434b | 113 | if (align == null) { |
cc3e7291 | 114 | align = Alignment.LEFT; |
451f434b NR |
115 | } |
116 | ||
cc3e7291 NR |
117 | align = align.undeprecate(); |
118 | ||
ec1f3444 NR |
119 | if (width >= 0) { |
120 | if (text == null) | |
121 | text = ""; | |
122 | ||
123 | int diff = width - text.length(); | |
124 | ||
125 | if (diff < 0) { | |
126 | if (cut) | |
127 | text = text.substring(0, width); | |
128 | } else if (diff > 0) { | |
cc3e7291 NR |
129 | if (diff < 2 && align != Alignment.RIGHT) |
130 | align = Alignment.LEFT; | |
ec1f3444 NR |
131 | |
132 | switch (align) { | |
cc3e7291 | 133 | case RIGHT: |
ec1f3444 NR |
134 | text = new String(new char[diff]).replace('\0', ' ') + text; |
135 | break; | |
cc3e7291 | 136 | case CENTER: |
ec1f3444 NR |
137 | int pad1 = (diff) / 2; |
138 | int pad2 = (diff + 1) / 2; | |
139 | text = new String(new char[pad1]).replace('\0', ' ') + text | |
140 | + new String(new char[pad2]).replace('\0', ' '); | |
141 | break; | |
cc3e7291 NR |
142 | case LEFT: |
143 | default: | |
144 | text = text + new String(new char[diff]).replace('\0', ' '); | |
145 | break; | |
ec1f3444 NR |
146 | } |
147 | } | |
148 | } | |
149 | ||
150 | return text; | |
151 | } | |
152 | ||
cc3e7291 NR |
153 | /** |
154 | * Justify a text into width-sized (at the maximum) lines. | |
155 | * | |
156 | * @param text | |
157 | * the {@link String} to justify | |
158 | * @param width | |
159 | * the maximum size of the resulting lines | |
160 | * | |
161 | * @return a list of justified text lines | |
162 | */ | |
163 | static public List<String> justifyText(String text, int width) { | |
164 | return justifyText(text, width, null); | |
165 | } | |
166 | ||
167 | /** | |
168 | * Justify a text into width-sized (at the maximum) lines. | |
169 | * | |
170 | * @param text | |
171 | * the {@link String} to justify | |
172 | * @param width | |
173 | * the maximum size of the resulting lines | |
174 | * @param align | |
175 | * align the lines in this position (default is | |
176 | * Alignment.Beginning) | |
177 | * | |
178 | * @return a list of justified text lines | |
179 | */ | |
180 | static public List<String> justifyText(String text, int width, | |
181 | Alignment align) { | |
182 | if (align == null) { | |
183 | align = Alignment.LEFT; | |
184 | } | |
185 | ||
186 | align = align.undeprecate(); | |
187 | ||
188 | switch (align) { | |
189 | case CENTER: | |
190 | return StringJustifier.center(text, width); | |
191 | case RIGHT: | |
192 | return StringJustifier.right(text, width); | |
193 | case JUSTIFY: | |
194 | return StringJustifier.full(text, width); | |
195 | case LEFT: | |
196 | default: | |
197 | return StringJustifier.left(text, width); | |
198 | } | |
199 | } | |
200 | ||
dc22eb95 NR |
201 | /** |
202 | * Justify a text into width-sized (at the maximum) lines. | |
203 | * | |
204 | * @param text | |
205 | * the {@link String} to justify | |
206 | * @param width | |
207 | * the maximum size of the resulting lines | |
208 | * | |
209 | * @return a list of justified text lines | |
210 | */ | |
211 | static public List<String> justifyText(List<String> text, int width) { | |
212 | return justifyText(text, width, null); | |
213 | } | |
214 | ||
215 | /** | |
216 | * Justify a text into width-sized (at the maximum) lines. | |
217 | * | |
218 | * @param text | |
219 | * the {@link String} to justify | |
220 | * @param width | |
221 | * the maximum size of the resulting lines | |
222 | * @param align | |
223 | * align the lines in this position (default is | |
224 | * Alignment.Beginning) | |
225 | * | |
226 | * @return a list of justified text lines | |
227 | */ | |
228 | static public List<String> justifyText(List<String> text, int width, | |
229 | Alignment align) { | |
230 | List<String> result = new ArrayList<String>(); | |
231 | ||
232 | // Content <-> Bullet spacing (null = no spacing) | |
233 | List<Entry<String, String>> lines = new ArrayList<Entry<String, String>>(); | |
234 | StringBuilder previous = null; | |
235 | StringBuilder tmp = new StringBuilder(); | |
236 | String previousItemBulletSpacing = null; | |
237 | String itemBulletSpacing = null; | |
238 | for (String inputLine : text) { | |
239 | boolean previousLineComplete = true; | |
240 | ||
241 | String current = inputLine.replace("\t", " "); | |
242 | itemBulletSpacing = getItemSpacing(current); | |
243 | boolean bullet = isItemLine(current); | |
244 | if ((previousItemBulletSpacing == null || itemBulletSpacing | |
245 | .length() <= previousItemBulletSpacing.length()) && !bullet) { | |
246 | itemBulletSpacing = null; | |
247 | } | |
248 | ||
249 | if (itemBulletSpacing != null) { | |
250 | current = current.trim(); | |
251 | if (!current.isEmpty() && bullet) { | |
252 | current = current.substring(1); | |
253 | } | |
254 | current = current.trim(); | |
255 | previousLineComplete = bullet; | |
256 | } else { | |
257 | tmp.setLength(0); | |
258 | for (String word : current.split(" ")) { | |
259 | if (word.isEmpty()) { | |
260 | continue; | |
261 | } | |
262 | ||
263 | if (tmp.length() > 0) { | |
264 | tmp.append(' '); | |
265 | } | |
266 | tmp.append(word.trim()); | |
267 | } | |
268 | current = tmp.toString(); | |
269 | ||
270 | previousLineComplete = current.isEmpty() | |
271 | || previousItemBulletSpacing != null | |
c0c091af NR |
272 | || (previous != null && isFullLine(previous)) |
273 | || isHrLine(current) || isHrLine(previous); | |
dc22eb95 NR |
274 | } |
275 | ||
276 | if (previous == null) { | |
277 | previous = new StringBuilder(); | |
278 | } else { | |
279 | if (previousLineComplete) { | |
280 | lines.add(new AbstractMap.SimpleEntry<String, String>( | |
281 | previous.toString(), previousItemBulletSpacing)); | |
282 | previous.setLength(0); | |
283 | previousItemBulletSpacing = itemBulletSpacing; | |
284 | } else { | |
285 | previous.append(' '); | |
286 | } | |
287 | } | |
288 | ||
289 | previous.append(current); | |
290 | ||
291 | } | |
292 | ||
293 | if (previous != null) { | |
294 | lines.add(new AbstractMap.SimpleEntry<String, String>(previous | |
295 | .toString(), previousItemBulletSpacing)); | |
296 | } | |
297 | ||
298 | for (Entry<String, String> line : lines) { | |
299 | String content = line.getKey(); | |
300 | String spacing = line.getValue(); | |
301 | ||
302 | String bullet = "- "; | |
303 | if (spacing == null) { | |
304 | bullet = ""; | |
305 | spacing = ""; | |
306 | } | |
307 | ||
308 | if (spacing.length() > width + 3) { | |
309 | spacing = ""; | |
310 | } | |
311 | ||
312 | for (String subline : StringUtils.justifyText(content, width | |
313 | - (spacing.length() + bullet.length()), align)) { | |
314 | result.add(spacing + bullet + subline); | |
315 | if (!bullet.isEmpty()) { | |
316 | bullet = " "; | |
317 | } | |
318 | } | |
319 | } | |
320 | ||
321 | return result; | |
322 | } | |
323 | ||
ec1f3444 NR |
324 | /** |
325 | * Sanitise the given input to make it more Terminal-friendly by removing | |
326 | * combining characters. | |
327 | * | |
328 | * @param input | |
329 | * the input to sanitise | |
330 | * @param allowUnicode | |
331 | * allow Unicode or only allow ASCII Latin characters | |
332 | * | |
333 | * @return the sanitised {@link String} | |
334 | */ | |
335 | static public String sanitize(String input, boolean allowUnicode) { | |
336 | return sanitize(input, allowUnicode, !allowUnicode); | |
337 | } | |
338 | ||
339 | /** | |
340 | * Sanitise the given input to make it more Terminal-friendly by removing | |
341 | * combining characters. | |
342 | * | |
343 | * @param input | |
344 | * the input to sanitise | |
345 | * @param allowUnicode | |
346 | * allow Unicode or only allow ASCII Latin characters | |
347 | * @param removeAllAccents | |
348 | * TRUE to replace all accentuated characters by their non | |
349 | * accentuated counter-parts | |
350 | * | |
351 | * @return the sanitised {@link String} | |
352 | */ | |
353 | static public String sanitize(String input, boolean allowUnicode, | |
354 | boolean removeAllAccents) { | |
355 | ||
356 | if (removeAllAccents) { | |
357 | input = Normalizer.normalize(input, Form.NFKD); | |
e8aa5bf9 NR |
358 | if (marks != null) { |
359 | input = marks.matcher(input).replaceAll(""); | |
360 | } | |
ec1f3444 NR |
361 | } |
362 | ||
363 | input = Normalizer.normalize(input, Form.NFKC); | |
364 | ||
365 | if (!allowUnicode) { | |
366 | StringBuilder builder = new StringBuilder(); | |
367 | for (int index = 0; index < input.length(); index++) { | |
368 | char car = input.charAt(index); | |
369 | // displayable chars in ASCII are in the range 32<->255, | |
370 | // except DEL (127) | |
371 | if (car >= 32 && car <= 255 && car != 127) { | |
372 | builder.append(car); | |
373 | } | |
374 | } | |
375 | input = builder.toString(); | |
376 | } | |
377 | ||
378 | return input; | |
379 | } | |
380 | ||
381 | /** | |
451f434b NR |
382 | * Convert between the time in milliseconds to a {@link String} in a "fixed" |
383 | * way (to exchange data over the wire, for instance). | |
384 | * <p> | |
385 | * Precise to the second. | |
ec1f3444 NR |
386 | * |
387 | * @param time | |
451f434b NR |
388 | * the specified number of milliseconds since the standard base |
389 | * time known as "the epoch", namely January 1, 1970, 00:00:00 | |
390 | * GMT | |
ec1f3444 NR |
391 | * |
392 | * @return the time as a {@link String} | |
393 | */ | |
394 | static public String fromTime(long time) { | |
395 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |
396 | return sdf.format(new Date(time)); | |
397 | } | |
398 | ||
399 | /** | |
451f434b | 400 | * Convert between the time as a {@link String} to milliseconds in a "fixed" |
ec1f3444 | 401 | * way (to exchange data over the wire, for instance). |
451f434b NR |
402 | * <p> |
403 | * Precise to the second. | |
ec1f3444 | 404 | * |
db31c358 | 405 | * @param displayTime |
ec1f3444 NR |
406 | * the time as a {@link String} |
407 | * | |
451f434b | 408 | * @return the number of milliseconds since the standard base time known as |
e8aa5bf9 NR |
409 | * "the epoch", namely January 1, 1970, 00:00:00 GMT, or -1 in case |
410 | * of error | |
411 | * | |
412 | * @throws ParseException | |
413 | * in case of parse error | |
ec1f3444 | 414 | */ |
e8aa5bf9 | 415 | static public long toTime(String displayTime) throws ParseException { |
ec1f3444 | 416 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
e8aa5bf9 | 417 | return sdf.parse(displayTime).getTime(); |
ec1f3444 NR |
418 | } |
419 | ||
ec1f3444 NR |
420 | /** |
421 | * Return a hash of the given {@link String}. | |
422 | * | |
423 | * @param input | |
424 | * the input data | |
425 | * | |
426 | * @return the hash | |
427 | */ | |
b771aed5 | 428 | static public String getMd5Hash(String input) { |
ec1f3444 NR |
429 | try { |
430 | MessageDigest md = MessageDigest.getInstance("MD5"); | |
3f8349b7 | 431 | md.update(input.getBytes("UTF-8")); |
ec1f3444 NR |
432 | byte byteData[] = md.digest(); |
433 | ||
434 | StringBuffer hexString = new StringBuffer(); | |
435 | for (int i = 0; i < byteData.length; i++) { | |
436 | String hex = Integer.toHexString(0xff & byteData[i]); | |
437 | if (hex.length() == 1) | |
438 | hexString.append('0'); | |
439 | hexString.append(hex); | |
440 | } | |
441 | ||
442 | return hexString.toString(); | |
443 | } catch (NoSuchAlgorithmException e) { | |
444 | return input; | |
3f8349b7 NR |
445 | } catch (UnsupportedEncodingException e) { |
446 | return input; | |
ec1f3444 NR |
447 | } |
448 | } | |
449 | ||
ec1f3444 NR |
450 | /** |
451 | * Remove the HTML content from the given input, and un-html-ize the rest. | |
452 | * | |
453 | * @param html | |
454 | * the HTML-encoded content | |
455 | * | |
456 | * @return the HTML-free equivalent content | |
457 | */ | |
458 | public static String unhtml(String html) { | |
459 | StringBuilder builder = new StringBuilder(); | |
460 | ||
461 | int inTag = 0; | |
462 | for (char car : html.toCharArray()) { | |
463 | if (car == '<') { | |
464 | inTag++; | |
465 | } else if (car == '>') { | |
466 | inTag--; | |
467 | } else if (inTag <= 0) { | |
468 | builder.append(car); | |
469 | } | |
470 | } | |
471 | ||
7ee9568b NR |
472 | char nbsp = ' '; // non-breakable space (a special char) |
473 | char space = ' '; | |
474 | return HtmlEscape.unescapeHtml(builder.toString()).replace(nbsp, space); | |
ec1f3444 NR |
475 | } |
476 | ||
477 | /** | |
478 | * Escape the given {@link String} so it can be used in XML, as content. | |
479 | * | |
480 | * @param input | |
481 | * the input {@link String} | |
482 | * | |
483 | * @return the escaped {@link String} | |
484 | */ | |
485 | public static String xmlEscape(String input) { | |
486 | if (input == null) { | |
487 | return ""; | |
488 | } | |
489 | ||
490 | return HtmlEscape.escapeHtml(input, | |
491 | HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, | |
492 | HtmlEscapeLevel.LEVEL_1_ONLY_MARKUP_SIGNIFICANT); | |
493 | } | |
494 | ||
495 | /** | |
496 | * Escape the given {@link String} so it can be used in XML, as text content | |
497 | * inside double-quotes. | |
498 | * | |
499 | * @param input | |
500 | * the input {@link String} | |
501 | * | |
502 | * @return the escaped {@link String} | |
503 | */ | |
504 | public static String xmlEscapeQuote(String input) { | |
505 | if (input == null) { | |
506 | return ""; | |
507 | } | |
508 | ||
509 | return HtmlEscape.escapeHtml(input, | |
510 | HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, | |
511 | HtmlEscapeLevel.LEVEL_1_ONLY_MARKUP_SIGNIFICANT); | |
512 | } | |
db31c358 | 513 | |
80500544 NR |
514 | /** |
515 | * Zip the data and then encode it into Base64. | |
516 | * | |
bb60bd13 NR |
517 | * @deprecated use {@link StringUtils#base64(byte[], boolean)} with the |
518 | * correct parameter instead | |
519 | * | |
80500544 NR |
520 | * @param data |
521 | * the data | |
522 | * | |
523 | * @return the Base64 zipped version | |
524 | */ | |
bb60bd13 | 525 | @Deprecated |
db31c358 NR |
526 | public static String zip64(String data) { |
527 | try { | |
a359464f | 528 | return Base64.encodeBytes(data.getBytes("UTF-8"), Base64.GZIP); |
db31c358 NR |
529 | } catch (IOException e) { |
530 | e.printStackTrace(); | |
531 | return null; | |
532 | } | |
533 | } | |
534 | ||
80500544 NR |
535 | /** |
536 | * Unconvert from Base64 then unzip the content. | |
537 | * | |
bb60bd13 NR |
538 | * @deprecated use {@link StringUtils#unbase64s(String, boolean)} with the |
539 | * correct parameter instead | |
540 | * | |
80500544 NR |
541 | * @param data |
542 | * the data in Base64 format | |
543 | * | |
544 | * @return the raw data | |
545 | * | |
546 | * @throws IOException | |
547 | * in case of I/O error | |
548 | */ | |
bb60bd13 | 549 | @Deprecated |
db31c358 NR |
550 | public static String unzip64(String data) throws IOException { |
551 | ByteArrayInputStream in = new ByteArrayInputStream(Base64.decode(data, | |
552 | Base64.GZIP)); | |
553 | ||
554 | Scanner scan = new Scanner(in); | |
555 | scan.useDelimiter("\\A"); | |
556 | try { | |
557 | return scan.next(); | |
558 | } finally { | |
559 | scan.close(); | |
560 | } | |
561 | } | |
e8aa5bf9 | 562 | |
a359464f NR |
563 | /** |
564 | * Convert the given data to Base64 format. | |
565 | * | |
566 | * @param data | |
567 | * the data to convert | |
568 | * @param zip | |
569 | * TRUE to also compress the data in GZIP format; remember that | |
570 | * compressed and not-compressed content are different; you need | |
571 | * to know which is which when decoding | |
572 | * | |
573 | * @return the Base64 {@link String} representation of the data | |
574 | * | |
575 | * @throws IOException | |
576 | * in case of I/O errors | |
577 | */ | |
578 | public static String base64(String data, boolean zip) throws IOException { | |
579 | return base64(data.getBytes("UTF-8"), zip); | |
580 | } | |
581 | ||
bb60bd13 NR |
582 | /** |
583 | * Convert the given data to Base64 format. | |
584 | * | |
585 | * @param data | |
586 | * the data to convert | |
587 | * @param zip | |
588 | * TRUE to also compress the data in GZIP format; remember that | |
589 | * compressed and not-compressed content are different; you need | |
590 | * to know which is which when decoding | |
591 | * | |
592 | * @return the Base64 {@link String} representation of the data | |
593 | * | |
594 | * @throws IOException | |
595 | * in case of I/O errors | |
596 | */ | |
597 | public static String base64(byte[] data, boolean zip) throws IOException { | |
598 | return Base64.encodeBytes(data, zip ? Base64.GZIP : Base64.NO_OPTIONS); | |
599 | } | |
600 | ||
601 | /** | |
a359464f NR |
602 | * Convert the given data to Base64 format. |
603 | * | |
604 | * @param data | |
605 | * the data to convert | |
606 | * @param zip | |
607 | * TRUE to also uncompress the data from a GZIP format; take care | |
608 | * about this flag, as it could easily cause errors in the | |
609 | * returned content or an {@link IOException} | |
610 | * @param breakLines | |
611 | * TRUE to break lines on every 76th character | |
612 | * | |
613 | * @return the Base64 {@link String} representation of the data | |
614 | * | |
615 | * @throws IOException | |
616 | * in case of I/O errors | |
617 | */ | |
618 | public static OutputStream base64(OutputStream data, boolean zip, | |
619 | boolean breakLines) throws IOException { | |
620 | OutputStream out = new Base64.OutputStream(data, | |
621 | breakLines ? Base64.DO_BREAK_LINES & Base64.ENCODE | |
622 | : Base64.ENCODE); | |
623 | ||
624 | if (zip) { | |
625 | out = new java.util.zip.GZIPOutputStream(out); | |
626 | } | |
627 | ||
628 | return out; | |
629 | } | |
630 | ||
631 | /** | |
632 | * Convert the given data to Base64 format. | |
633 | * | |
634 | * @param data | |
635 | * the data to convert | |
636 | * @param zip | |
637 | * TRUE to also uncompress the data from a GZIP format; take care | |
638 | * about this flag, as it could easily cause errors in the | |
639 | * returned content or an {@link IOException} | |
640 | * @param breakLines | |
641 | * TRUE to break lines on every 76th character | |
642 | * | |
643 | * @return the Base64 {@link String} representation of the data | |
644 | * | |
645 | * @throws IOException | |
646 | * in case of I/O errors | |
647 | */ | |
648 | public static InputStream base64(InputStream data, boolean zip, | |
649 | boolean breakLines) throws IOException { | |
650 | if (zip) { | |
651 | data = new java.util.zip.GZIPInputStream(data); | |
652 | } | |
653 | ||
654 | return new Base64.InputStream(data, breakLines ? Base64.DO_BREAK_LINES | |
655 | & Base64.ENCODE : Base64.ENCODE); | |
656 | } | |
657 | ||
658 | /** | |
659 | * Unconvert the given data from Base64 format back to a raw array of bytes. | |
bb60bd13 NR |
660 | * |
661 | * @param data | |
662 | * the data to unconvert | |
663 | * @param zip | |
664 | * TRUE to also uncompress the data from a GZIP format; take care | |
665 | * about this flag, as it could easily cause errors in the | |
666 | * returned content or an {@link IOException} | |
667 | * | |
668 | * @return the raw data represented by the given Base64 {@link String}, | |
669 | * optionally compressed with GZIP | |
670 | * | |
671 | * @throws IOException | |
672 | * in case of I/O errors | |
673 | */ | |
674 | public static byte[] unbase64(String data, boolean zip) throws IOException { | |
675 | return Base64.decode(data, zip ? Base64.GZIP : Base64.NO_OPTIONS); | |
676 | } | |
677 | ||
a359464f NR |
678 | /** |
679 | * Unconvert the given data from Base64 format back to a raw array of bytes. | |
680 | * | |
681 | * @param data | |
682 | * the data to unconvert | |
683 | * @param zip | |
684 | * TRUE to also uncompress the data from a GZIP format; take care | |
685 | * about this flag, as it could easily cause errors in the | |
686 | * returned content or an {@link IOException} | |
687 | * @param breakLines | |
688 | * TRUE to break lines on every 76th character | |
689 | * | |
690 | * @return the raw data represented by the given Base64 {@link String} | |
691 | * | |
692 | * @throws IOException | |
693 | * in case of I/O errors | |
694 | */ | |
695 | public static OutputStream unbase64(OutputStream data, boolean zip, | |
696 | boolean breakLines) throws IOException { | |
697 | OutputStream out = new Base64.OutputStream(data, | |
698 | breakLines ? Base64.DO_BREAK_LINES & Base64.ENCODE | |
699 | : Base64.ENCODE); | |
700 | ||
701 | if (zip) { | |
702 | out = new java.util.zip.GZIPOutputStream(out); | |
703 | } | |
704 | ||
705 | return out; | |
706 | } | |
707 | ||
708 | /** | |
709 | * Unconvert the given data from Base64 format back to a raw array of bytes. | |
710 | * | |
711 | * @param data | |
712 | * the data to unconvert | |
713 | * @param zip | |
714 | * TRUE to also uncompress the data from a GZIP format; take care | |
715 | * about this flag, as it could easily cause errors in the | |
716 | * returned content or an {@link IOException} | |
717 | * @param breakLines | |
718 | * TRUE to break lines on every 76th character | |
719 | * | |
720 | * @return the raw data represented by the given Base64 {@link String} | |
721 | * | |
722 | * @throws IOException | |
723 | * in case of I/O errors | |
724 | */ | |
725 | public static InputStream unbase64(InputStream data, boolean zip, | |
726 | boolean breakLines) throws IOException { | |
727 | if (zip) { | |
728 | data = new java.util.zip.GZIPInputStream(data); | |
729 | } | |
730 | ||
731 | return new Base64.InputStream(data, breakLines ? Base64.DO_BREAK_LINES | |
732 | & Base64.ENCODE : Base64.ENCODE); | |
733 | } | |
734 | ||
bb60bd13 NR |
735 | /** |
736 | * Unonvert the given data from Base64 format back to a {@link String}. | |
737 | * | |
738 | * @param data | |
739 | * the data to unconvert | |
740 | * @param zip | |
741 | * TRUE to also uncompress the data from a GZIP format; take care | |
742 | * about this flag, as it could easily cause errors in the | |
743 | * returned content or an {@link IOException} | |
744 | * | |
745 | * @return the {@link String} represented by the given Base64 {@link String} | |
746 | * , optionally compressed with GZIP | |
747 | * | |
748 | * @throws IOException | |
749 | * in case of I/O errors | |
750 | */ | |
751 | public static String unbase64s(String data, boolean zip) throws IOException { | |
752 | ByteArrayInputStream in = new ByteArrayInputStream(unbase64(data, zip)); | |
753 | ||
a359464f | 754 | Scanner scan = new Scanner(in, "UTF-8"); |
bb60bd13 NR |
755 | scan.useDelimiter("\\A"); |
756 | try { | |
757 | return scan.next(); | |
758 | } finally { | |
759 | scan.close(); | |
760 | } | |
761 | } | |
762 | ||
e8aa5bf9 NR |
763 | /** |
764 | * The "remove accents" pattern. | |
765 | * | |
766 | * @return the pattern, or NULL if a problem happens | |
767 | */ | |
768 | private static Pattern getMarks() { | |
769 | try { | |
770 | return Pattern | |
771 | .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+"); | |
772 | } catch (Exception e) { | |
773 | // Can fail on Android... | |
774 | return null; | |
775 | } | |
776 | } | |
dc22eb95 | 777 | |
bb60bd13 | 778 | // |
dc22eb95 | 779 | // justify List<String> related: |
bb60bd13 | 780 | // |
dc22eb95 | 781 | |
bb60bd13 NR |
782 | /** |
783 | * Check if this line ends as a complete line (ends with a "." or similar). | |
784 | * <p> | |
785 | * Note that we consider an empty line as full, and a line ending with | |
786 | * spaces as not complete. | |
787 | * | |
788 | * @param line | |
789 | * the line to check | |
790 | * | |
791 | * @return TRUE if it does | |
792 | */ | |
dc22eb95 | 793 | static private boolean isFullLine(StringBuilder line) { |
bb60bd13 NR |
794 | if (line.length() == 0) { |
795 | return true; | |
796 | } | |
797 | ||
798 | char lastCar = line.charAt(line.length() - 1); | |
799 | switch (lastCar) { | |
800 | case '.': // points | |
801 | case '?': | |
802 | case '!': | |
803 | ||
804 | case '\'': // quotes | |
805 | case '‘': | |
806 | case '’': | |
807 | ||
808 | case '"': // double quotes | |
809 | case '”': | |
810 | case '“': | |
811 | case '»': | |
812 | case '«': | |
813 | return true; | |
814 | default: | |
815 | return false; | |
816 | } | |
dc22eb95 NR |
817 | } |
818 | ||
bb60bd13 NR |
819 | /** |
820 | * Check if this line represent an item in a list or description (i.e., | |
821 | * check that the first non-space char is "-"). | |
822 | * | |
823 | * @param line | |
824 | * the line to check | |
825 | * | |
826 | * @return TRUE if it is | |
827 | */ | |
dc22eb95 NR |
828 | static private boolean isItemLine(String line) { |
829 | String spacing = getItemSpacing(line); | |
c0c091af NR |
830 | return spacing != null && !spacing.isEmpty() |
831 | && line.charAt(spacing.length()) == '-'; | |
dc22eb95 NR |
832 | } |
833 | ||
bb60bd13 NR |
834 | /** |
835 | * Return all the spaces that start this line (or Empty if none). | |
836 | * | |
837 | * @param line | |
838 | * the line to get the starting spaces from | |
839 | * | |
840 | * @return the left spacing | |
841 | */ | |
dc22eb95 NR |
842 | static private String getItemSpacing(String line) { |
843 | int i; | |
844 | for (i = 0; i < line.length(); i++) { | |
845 | if (line.charAt(i) != ' ') { | |
846 | return line.substring(0, i); | |
847 | } | |
848 | } | |
849 | ||
850 | return ""; | |
851 | } | |
c0c091af | 852 | |
bb60bd13 NR |
853 | /** |
854 | * This line is an horizontal spacer line. | |
855 | * | |
856 | * @param line | |
857 | * the line to test | |
858 | * | |
859 | * @return TRUE if it is | |
860 | */ | |
c0c091af NR |
861 | static private boolean isHrLine(CharSequence line) { |
862 | int count = 0; | |
863 | if (line != null) { | |
864 | for (int i = 0; i < line.length(); i++) { | |
865 | char car = line.charAt(i); | |
866 | if (car == ' ' || car == '\t' || car == '*' || car == '-' | |
867 | || car == '_' || car == '~' || car == '=' || car == '/' | |
868 | || car == '\\') { | |
869 | count++; | |
870 | } else { | |
871 | return false; | |
872 | } | |
873 | } | |
874 | } | |
875 | ||
876 | return count > 2; | |
877 | } | |
ec1f3444 | 878 | } |