X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2FStringUtils.java;h=1884c21ff47a98fce6864c0c6a7c801727f0cb05;hb=a359464fcf59af8abc6f69ae0e88e42adc6018df;hp=9e5d654a8252ff3b2b94f4a6b610c016aa526f5c;hpb=cc3e72914add9c950f563bf37c6615e8721ad32e;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/StringUtils.java b/src/be/nikiroo/utils/StringUtils.java index 9e5d654..1884c21 100644 --- a/src/be/nikiroo/utils/StringUtils.java +++ b/src/be/nikiroo/utils/StringUtils.java @@ -2,6 +2,8 @@ package be.nikiroo.utils; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -9,8 +11,11 @@ import java.text.Normalizer; import java.text.Normalizer.Form; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map.Entry; import java.util.Scanner; import java.util.regex.Pattern; @@ -193,6 +198,129 @@ public class StringUtils { } } + /** + * Justify a text into width-sized (at the maximum) lines. + * + * @param text + * the {@link String} to justify + * @param width + * the maximum size of the resulting lines + * + * @return a list of justified text lines + */ + static public List justifyText(List text, int width) { + return justifyText(text, width, null); + } + + /** + * Justify a text into width-sized (at the maximum) lines. + * + * @param text + * the {@link String} to justify + * @param width + * the maximum size of the resulting lines + * @param align + * align the lines in this position (default is + * Alignment.Beginning) + * + * @return a list of justified text lines + */ + static public List justifyText(List text, int width, + Alignment align) { + List result = new ArrayList(); + + // Content <-> Bullet spacing (null = no spacing) + List> lines = new ArrayList>(); + StringBuilder previous = null; + StringBuilder tmp = new StringBuilder(); + String previousItemBulletSpacing = null; + String itemBulletSpacing = null; + for (String inputLine : text) { + boolean previousLineComplete = true; + + String current = inputLine.replace("\t", " "); + itemBulletSpacing = getItemSpacing(current); + boolean bullet = isItemLine(current); + if ((previousItemBulletSpacing == null || itemBulletSpacing + .length() <= previousItemBulletSpacing.length()) && !bullet) { + itemBulletSpacing = null; + } + + if (itemBulletSpacing != null) { + current = current.trim(); + if (!current.isEmpty() && bullet) { + current = current.substring(1); + } + current = current.trim(); + previousLineComplete = bullet; + } else { + tmp.setLength(0); + for (String word : current.split(" ")) { + if (word.isEmpty()) { + continue; + } + + if (tmp.length() > 0) { + tmp.append(' '); + } + tmp.append(word.trim()); + } + current = tmp.toString(); + + previousLineComplete = current.isEmpty() + || previousItemBulletSpacing != null + || (previous != null && isFullLine(previous)) + || isHrLine(current) || isHrLine(previous); + } + + if (previous == null) { + previous = new StringBuilder(); + } else { + if (previousLineComplete) { + lines.add(new AbstractMap.SimpleEntry( + previous.toString(), previousItemBulletSpacing)); + previous.setLength(0); + previousItemBulletSpacing = itemBulletSpacing; + } else { + previous.append(' '); + } + } + + previous.append(current); + + } + + if (previous != null) { + lines.add(new AbstractMap.SimpleEntry(previous + .toString(), previousItemBulletSpacing)); + } + + for (Entry line : lines) { + String content = line.getKey(); + String spacing = line.getValue(); + + String bullet = "- "; + if (spacing == null) { + bullet = ""; + spacing = ""; + } + + if (spacing.length() > width + 3) { + spacing = ""; + } + + for (String subline : StringUtils.justifyText(content, width + - (spacing.length() + bullet.length()), align)) { + result.add(spacing + bullet + subline); + if (!bullet.isEmpty()) { + bullet = " "; + } + } + } + + return result; + } + /** * Sanitise the given input to make it more Terminal-friendly by removing * combining characters. @@ -386,14 +514,18 @@ public class StringUtils { /** * Zip the data and then encode it into Base64. * + * @deprecated use {@link StringUtils#base64(byte[], boolean)} with the + * correct parameter instead + * * @param data * the data * * @return the Base64 zipped version */ + @Deprecated public static String zip64(String data) { try { - return Base64.encodeBytes(data.getBytes(), Base64.GZIP); + return Base64.encodeBytes(data.getBytes("UTF-8"), Base64.GZIP); } catch (IOException e) { e.printStackTrace(); return null; @@ -403,6 +535,9 @@ public class StringUtils { /** * Unconvert from Base64 then unzip the content. * + * @deprecated use {@link StringUtils#unbase64s(String, boolean)} with the + * correct parameter instead + * * @param data * the data in Base64 format * @@ -411,6 +546,7 @@ public class StringUtils { * @throws IOException * in case of I/O error */ + @Deprecated public static String unzip64(String data) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(Base64.decode(data, Base64.GZIP)); @@ -424,6 +560,206 @@ public class StringUtils { } } + /** + * Convert the given data to Base64 format. + * + * @param data + * the data to convert + * @param zip + * TRUE to also compress the data in GZIP format; remember that + * compressed and not-compressed content are different; you need + * to know which is which when decoding + * + * @return the Base64 {@link String} representation of the data + * + * @throws IOException + * in case of I/O errors + */ + public static String base64(String data, boolean zip) throws IOException { + return base64(data.getBytes("UTF-8"), zip); + } + + /** + * Convert the given data to Base64 format. + * + * @param data + * the data to convert + * @param zip + * TRUE to also compress the data in GZIP format; remember that + * compressed and not-compressed content are different; you need + * to know which is which when decoding + * + * @return the Base64 {@link String} representation of the data + * + * @throws IOException + * in case of I/O errors + */ + public static String base64(byte[] data, boolean zip) throws IOException { + return Base64.encodeBytes(data, zip ? Base64.GZIP : Base64.NO_OPTIONS); + } + + /** + * Convert the given data to Base64 format. + * + * @param data + * the data to convert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * @param breakLines + * TRUE to break lines on every 76th character + * + * @return the Base64 {@link String} representation of the data + * + * @throws IOException + * in case of I/O errors + */ + public static OutputStream base64(OutputStream data, boolean zip, + boolean breakLines) throws IOException { + OutputStream out = new Base64.OutputStream(data, + breakLines ? Base64.DO_BREAK_LINES & Base64.ENCODE + : Base64.ENCODE); + + if (zip) { + out = new java.util.zip.GZIPOutputStream(out); + } + + return out; + } + + /** + * Convert the given data to Base64 format. + * + * @param data + * the data to convert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * @param breakLines + * TRUE to break lines on every 76th character + * + * @return the Base64 {@link String} representation of the data + * + * @throws IOException + * in case of I/O errors + */ + public static InputStream base64(InputStream data, boolean zip, + boolean breakLines) throws IOException { + if (zip) { + data = new java.util.zip.GZIPInputStream(data); + } + + return new Base64.InputStream(data, breakLines ? Base64.DO_BREAK_LINES + & Base64.ENCODE : Base64.ENCODE); + } + + /** + * Unconvert the given data from Base64 format back to a raw array of bytes. + * + * @param data + * the data to unconvert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * + * @return the raw data represented by the given Base64 {@link String}, + * optionally compressed with GZIP + * + * @throws IOException + * in case of I/O errors + */ + public static byte[] unbase64(String data, boolean zip) throws IOException { + return Base64.decode(data, zip ? Base64.GZIP : Base64.NO_OPTIONS); + } + + /** + * Unconvert the given data from Base64 format back to a raw array of bytes. + * + * @param data + * the data to unconvert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * @param breakLines + * TRUE to break lines on every 76th character + * + * @return the raw data represented by the given Base64 {@link String} + * + * @throws IOException + * in case of I/O errors + */ + public static OutputStream unbase64(OutputStream data, boolean zip, + boolean breakLines) throws IOException { + OutputStream out = new Base64.OutputStream(data, + breakLines ? Base64.DO_BREAK_LINES & Base64.ENCODE + : Base64.ENCODE); + + if (zip) { + out = new java.util.zip.GZIPOutputStream(out); + } + + return out; + } + + /** + * Unconvert the given data from Base64 format back to a raw array of bytes. + * + * @param data + * the data to unconvert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * @param breakLines + * TRUE to break lines on every 76th character + * + * @return the raw data represented by the given Base64 {@link String} + * + * @throws IOException + * in case of I/O errors + */ + public static InputStream unbase64(InputStream data, boolean zip, + boolean breakLines) throws IOException { + if (zip) { + data = new java.util.zip.GZIPInputStream(data); + } + + return new Base64.InputStream(data, breakLines ? Base64.DO_BREAK_LINES + & Base64.ENCODE : Base64.ENCODE); + } + + /** + * Unonvert the given data from Base64 format back to a {@link String}. + * + * @param data + * the data to unconvert + * @param zip + * TRUE to also uncompress the data from a GZIP format; take care + * about this flag, as it could easily cause errors in the + * returned content or an {@link IOException} + * + * @return the {@link String} represented by the given Base64 {@link String} + * , optionally compressed with GZIP + * + * @throws IOException + * in case of I/O errors + */ + public static String unbase64s(String data, boolean zip) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(unbase64(data, zip)); + + Scanner scan = new Scanner(in, "UTF-8"); + scan.useDelimiter("\\A"); + try { + return scan.next(); + } finally { + scan.close(); + } + } + /** * The "remove accents" pattern. * @@ -438,4 +774,105 @@ public class StringUtils { return null; } } + + // + // justify List related: + // + + /** + * Check if this line ends as a complete line (ends with a "." or similar). + *

+ * Note that we consider an empty line as full, and a line ending with + * spaces as not complete. + * + * @param line + * the line to check + * + * @return TRUE if it does + */ + static private boolean isFullLine(StringBuilder line) { + if (line.length() == 0) { + return true; + } + + char lastCar = line.charAt(line.length() - 1); + switch (lastCar) { + case '.': // points + case '?': + case '!': + + case '\'': // quotes + case '‘': + case '’': + + case '"': // double quotes + case '”': + case '“': + case '»': + case '«': + return true; + default: + return false; + } + } + + /** + * Check if this line represent an item in a list or description (i.e., + * check that the first non-space char is "-"). + * + * @param line + * the line to check + * + * @return TRUE if it is + */ + static private boolean isItemLine(String line) { + String spacing = getItemSpacing(line); + return spacing != null && !spacing.isEmpty() + && line.charAt(spacing.length()) == '-'; + } + + /** + * Return all the spaces that start this line (or Empty if none). + * + * @param line + * the line to get the starting spaces from + * + * @return the left spacing + */ + static private String getItemSpacing(String line) { + int i; + for (i = 0; i < line.length(); i++) { + if (line.charAt(i) != ' ') { + return line.substring(0, i); + } + } + + return ""; + } + + /** + * This line is an horizontal spacer line. + * + * @param line + * the line to test + * + * @return TRUE if it is + */ + static private boolean isHrLine(CharSequence line) { + int count = 0; + if (line != null) { + for (int i = 0; i < line.length(); i++) { + char car = line.charAt(i); + if (car == ' ' || car == '\t' || car == '*' || car == '-' + || car == '_' || car == '~' || car == '=' || car == '/' + || car == '\\') { + count++; + } else { + return false; + } + } + } + + return count > 2; + } }