| package org.freemarker.docgen; |
| |
| final class TextUtil { |
| |
| // Can't be instantiated |
| private TextUtil() { |
| // nop |
| } |
| |
| /** |
| * Quotes string as Java language string literal. |
| */ |
| public static String jQuote(String s) { |
| if (s == null) { |
| return "null"; |
| } |
| String s2; |
| int ln = s.length(); |
| int next = 0; |
| int i = 0; |
| StringBuilder b = new StringBuilder(ln + 3); |
| b.append("\""); |
| while (i < ln) { |
| char c = s.charAt(i); |
| if (c == '\\' || c == '"' || c < 0x20) { |
| b.append(s.substring(next, i)); |
| switch (c) { |
| case '\\': |
| b.append("\\\\"); break; |
| case '"': |
| b.append("\\\""); break; |
| case '\n': |
| b.append("\\n"); break; |
| case '\r': |
| b.append("\\r"); break; |
| case '\t': |
| b.append("\\t"); break; |
| case '\b': |
| b.append("\\b"); break; |
| case '\f': |
| b.append("\\f"); break; |
| default: |
| b.append("\\u0000"); |
| int x = b.length(); |
| s2 = Integer.toHexString(c); |
| b.replace(x - s2.length(), x, s2); |
| } |
| next = i + 1; |
| } |
| i++; |
| } |
| if (next < ln) { |
| b.append(s.substring(next)); |
| } |
| b.append("\""); |
| return b.toString(); |
| } |
| |
| /** |
| * Quotes character as Java language character, except quote characters, |
| * which are referred with name. |
| */ |
| public static String jQuoteOrName(char c) { |
| if (c == '\\' || c == '\'' || c == '"' || c < 0x20) { |
| switch (c) { |
| case '\\': |
| return "'\\\\'"; |
| case '\'': |
| return "\"apostrophe-quote\""; |
| case '"': |
| return "\"quotation mark\""; |
| case '\n': |
| return "'\\n'"; |
| case '\r': |
| return "'\\r'"; |
| case '\t': |
| return "'\\t'"; |
| case '\b': |
| return "'\\b'"; |
| case '\f': |
| return "'\\f'"; |
| default: |
| String s = Integer.toHexString(c); |
| int ln = s.length(); |
| if (ln == 1) { |
| return "'\\u000" + s + "'"; |
| } else if (ln == 2) { |
| return "'\\u00" + s + "'"; |
| } else if (ln == 3) { |
| return "'\\u0" + s + "'"; |
| } else { |
| return "'\\u" + s + "'"; |
| } |
| } |
| } else { |
| return "'" + c + "'"; |
| } |
| } |
| |
| private static final char[] UPPER_ROMAN_DIGITS = new char[] { |
| 'I', 'V', |
| 'X', 'L', |
| 'C', 'D', |
| 'M' |
| }; |
| |
| private static final char[] LOWER_ROMAN_DIGITS = new char[] { |
| 'i', 'v', |
| 'x', 'l', |
| 'c', 'd', |
| 'm' |
| }; |
| |
| /** |
| * Converts a number to upper-case Roman number, like XVI; up to 3999. |
| * @throws IllegalArgumentException if the number is not in the [1..3999] |
| * range. |
| */ |
| public static String toUpperRomanNumber(int n) { |
| return toRomanNumber(n, UPPER_ROMAN_DIGITS); |
| } |
| |
| /** |
| * Converts a number to lower-case Roman number, like xvi; up to 3999. |
| * @throws IllegalArgumentException if the number is not in the [1..3999] |
| * range. |
| */ |
| public static String toLowerRomanNumber(int n) { |
| return toRomanNumber(n, LOWER_ROMAN_DIGITS); |
| } |
| |
| private static String toRomanNumber(int n, char[] romanDigits) { |
| // We fetch the decimal digits from right to left. |
| // The res buffer will contain the Roman number *backwards*, and thus it |
| // also will contain the Roman "digits" backwards, like 7 will be "IIV". |
| // At the very end the buffer is reversed. |
| |
| if (n > 3999) { |
| throw new IllegalArgumentException("toRomanNumber only supports " |
| + "numbers in the [1..3999] range, but the number was " |
| + n + "."); |
| } |
| |
| StringBuilder res = new StringBuilder(); |
| int base = 0; |
| while (n != 0) { |
| int digit = n % 10; |
| n /= 10; |
| if (digit != 0) { |
| switch (digit) { |
| case 3: |
| res.append(romanDigits[base]); |
| // falls through |
| case 2: |
| res.append(romanDigits[base]); |
| // falls through |
| case 1: |
| res.append(romanDigits[base]); |
| break; |
| |
| case 4: |
| res.append(romanDigits[base + 1]) |
| .append(romanDigits[base]); |
| break; |
| |
| case 8: |
| res.append(romanDigits[base]); |
| // falls through |
| case 7: |
| res.append(romanDigits[base]); |
| // falls through |
| case 6: |
| res.append(romanDigits[base]); |
| // falls through |
| case 5: |
| res.append(romanDigits[base + 1]); |
| break; |
| |
| case 9: |
| res.append(romanDigits[base + 2]); |
| res.append(romanDigits[base]); |
| break; |
| |
| default: |
| throw new BugException("Unexpected branch"); |
| } |
| } |
| base += 2; |
| } |
| return res.reverse().toString(); |
| } |
| |
| /** |
| * Converts a number to upper-case Latin (alpha) number, like |
| * A, B, C, and so on, then Z, AA, AB, etc. |
| */ |
| public static String toUpperLatinNumber(int n) { |
| return toLatinNumber(n, 'A'); |
| } |
| |
| /** |
| * Converts a number to lower-case Latin (alpha) number, like |
| * a, b, c, and so on, then z, aa, ab, etc. |
| */ |
| public static String toLowerLatinNumber(int n) { |
| return toLatinNumber(n, 'a'); |
| } |
| |
| private static String toLatinNumber(final int n, char oneDigit) { |
| if (n < 1) { |
| throw new IllegalArgumentException("Can't convert 0 or negative " |
| + "numbers to latin-number."); |
| } |
| |
| // First find out how many "digits" will we need. We start from A, then |
| // try AA, then AAA, etc. (Note that the smallest digit is "A", which is |
| // 1, not 0. Hence this isn't like a usual 26-based number-system): |
| int reached = 1; |
| int weight = 1; |
| while (true) { |
| int nextWeight = weight * 26; |
| int nextReached = reached + nextWeight; |
| if (nextReached <= n) { |
| // So we will have one more digit |
| weight = nextWeight; |
| reached = nextReached; |
| } else { |
| // No more digits |
| break; |
| } |
| } |
| |
| // Increase the digits of the place values until we get as close |
| // to n as possible (but don't step over it). |
| StringBuilder sb = new StringBuilder(); |
| while (weight != 0) { |
| // digitIncrease: how many we increase the digit which is already 1 |
| final int digitIncrease = (n - reached) / weight; |
| sb.append((char) (oneDigit + digitIncrease)); |
| reached += digitIncrease * weight; |
| |
| weight /= 26; |
| } |
| |
| return sb.toString(); |
| } |
| |
| } |