2
\$\begingroup\$

I have a port of titlecase, originally in Perl and ported to other languages such as JavaScript and Python.

import android.text.TextUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Titlecase {
    protected Titlecase() {}

    private static List<String> SMALL = Arrays.asList("a", "an", "and", "as", "at", "but", "by", "en", "for", "if", "in", "of", "on", "or", "the", "to", "v", "v.", "via", "vs", "vs.");
    private static Pattern ALL_CAPS = Pattern.compile("[\\p{Upper}\\p{Punct}]+");
    private static Pattern LOWER = Pattern.compile("[\\p{Lower}]+");
    private static Pattern CAMEL = Pattern.compile("[\\p{Lower}]+[\\p{Upper}]+");
    private static Pattern PUNCT = Pattern.compile("[\\p{Punct}]+");

    public static boolean isShouting(String s) {
        Matcher $ = LOWER.matcher(s);
        return !$.find();
    }

    public static String get(String s) {
        List<String> processed = new ArrayList<String>();
        String[] lines = s.split("\\n");
        for (String l : lines) {
            List<String> tc_line = new ArrayList<String>();
            String[] words = l.split("\\s+");
            for (String word : words) {
                if (word == null || word.isEmpty()) {
                    continue;
                } else if (word.contains("-")) {
                    List<String> tc_dashes = new ArrayList<String>();
                    String[] dashes = word.split("-");
                    for (String d : dashes) {
                        tc_dashes.add(processWord(d));
                    }
                    tc_line.add(TextUtils.join("-", tc_dashes));
                } else if (word.contains("//") || '/' == word.charAt(0)) {
                    tc_line.add(word);
                } else if (word.contains("/")) {
                    List<String> tc_dashes = new ArrayList<String>();
                    String[] dashes = word.split("/");
                    for (String d : dashes) {
                        tc_dashes.add(processWord(d));
                    }
                    tc_line.add(TextUtils.join("/", tc_dashes));
                }
                else {
                    tc_line.add(processWord(word));
                }
            }
            if (tc_line.size() > 0) {
                tc_line.set(0, processWord(tc_line.get(0), true));
                tc_line.set(tc_line.size() - 1, processWord(tc_line.get(tc_line.size() - 1), true));
            }
            processed.add(TextUtils.join(" ", tc_line));
        }
        return TextUtils.join("\n", processed);
    }

    private static String processWord(String word) {
        return processWord(word, false);
    }

    private static String processWord(String word, boolean forceSmallToUpper) {
        if (word.contains("_") || word.contains("@")) {
            return word;
        }


        Matcher $ = CAMEL.matcher(word);
        if ($.find()) {
            return word;
        }

        $ = PUNCT.matcher(Character.toString(word.charAt(0)));
        if ($.matches()) {
            if (word.length() > 1) {
                return Character.toString(word.charAt(0)) +
                        processWord(word.substring(1), true);
            } else {
                return word;
            }
        }

        $ = ALL_CAPS.matcher(word);
        if ($.matches()) {
            if (word.contains(".")) {
                return word;
            } else {
                word = word.toLowerCase();
            }
        }

        if (word.length() > 3) {
            if (("`" == Character.toString(word.charAt(1)) ) ||
                    ("'" == String.valueOf(word.charAt(1))) ||
                    ("’" == String.valueOf(word.charAt(1)))) {
                word = Character.toString(word.charAt(0)).toUpperCase() +
                        Character.toString(word.charAt(1)) +
                        Character.toString(word.charAt(2)).toUpperCase() +
                        word.substring(3);
                return word;
            }
        }

        if (!forceSmallToUpper && SMALL.contains(word)) {
            return word;
        }

        if (word.length() > 1) {
            word = Character.toString(word.charAt(0)).toUpperCase() +
                    word.substring(1);
        } else {
            word = word.toUpperCase();
        }

        return word;
    }
}
\$\endgroup\$
2
\$\begingroup\$

It looks a bit too idiomatically Perl to me when it comes to variable naming:

  • tc_lines should be tcLines (or even something different, to make it easier to understand what it represents). Same for tc_dashes.
  • l (in for (String l : lines)) could be line, no need to save 3 chars. ;)
  • While Java allows for full Unicode identifiers, it's definitely non standard to name something $. At least if it's not something representing an amount of money.
  • I would use static final variables for most of the String constants you have, like "/"

processWord

I personally prefer not to use a variables to hold an object if I'm using them in exactly one place:

public static boolean isShouting(String s) {
    Matcher $ = LOWER.matcher(s);
    return !$.find();
}

could be

public static boolean isShouting(String s) {
    return !LOWER.matcher(s).find();
}

and the same with other instances of the $ variable. I just find it easier to read, but this might be just personal taste.

This here does not seem right:

if (("`" == Character.toString(word.charAt(1)) ) ||
                ("'" == String.valueOf(word.charAt(1))) ||
                ("’" == String.valueOf(word.charAt(1)))) {

Strings are objects and therefore the comparison will most probably fail. You could rewrite it as:

char second = word.charAt(1);
if (second == '`' || second == '\'' || second == '’') {

This:

       word = Character.toString(word.charAt(0)).toUpperCase() +
                Character.toString(word.charAt(1)) +
                Character.toString(word.charAt(2)).toUpperCase() +
                word.substring(3);

could make use of a StringBuilder to prevent constant String allocation, and use the Character.toUpperCase methods to prevent even more unnecessary String creation:

    word = new StringBuilder(word.length())
        .append(Character.toUpperCase(word.charAt(0)))
        .append(word.charAt(1))
        .append(Character.toUpperCase(word.charAt(2)))
        .append(word.substring(3))
        .toString();

This:

    if (word.length() > 1) {
        word = Character.toString(word.charAt(0)).toUpperCase() +
                word.substring(1);
    } else {
        word = word.toUpperCase();
    }

does exactly the same as this:

    word = Character.toUpperCase(word.charAt(0)) + word.substring(1);

given that substring will return the empty string if it's passed the total length of the string. It's smaller, maybe a bit clearer, and might even be more efficient given that it avoids an if (and the string addition of an empty string might be totally ignored during execution). You can also use a StringBuilder there, and use the same code for the part before where it matches with PUNCT.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.