diff --git a/src/scratchpad/src/org/apache/poi/hwpf/converter/NumberFormatter.java b/src/scratchpad/src/org/apache/poi/hwpf/converter/NumberFormatter.java index e5c7b32f0..42a72a9d9 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/converter/NumberFormatter.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/converter/NumberFormatter.java @@ -22,49 +22,117 @@ package org.apache.poi.hwpf.converter; import org.apache.poi.util.Beta; /** - * Comment me + * Utility class to translate numbers in letters, usually for lists. * - * @author Ryan Ackley + * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com) */ @Beta public final class NumberFormatter { - private static String[] C_LETTERS = new String[] { "a", "b", "c", "d", "e", - "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", - "s", "t", "u", "v", "x", "y", "z" }; + private static final String[] ENGLISH_LETTERS = new String[] { "a", "b", + "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; - private static String[] C_ROMAN = new String[] { "i", "ii", "iii", "iv", - "v", "vi", "vii", "viii", "ix", "x", "xi", "xii", "xiii", "xiv", - "xv", "xvi", "xvii", "xviii", "xix", "xx", "xxi", "xxii", "xxiii", - "xxiv", "xxv", "xxvi", "xxvii", "xxviii", "xxix", "xxx", "xxxi", - "xxxii", "xxxiii", "xxxiv", "xxxv", "xxxvi", "xxxvii", "xxxvii", - "xxxviii", "xxxix", "xl", "xli", "xlii", "xliii", "xliv", "xlv", - "xlvi", "xlvii", "xlviii", "xlix", "l" }; + private static final String[] ROMAN_LETTERS = { "m", "cm", "d", "cd", "c", + "xc", "l", "xl", "x", "ix", "v", "iv", "i" }; - private final static int T_ARABIC = 0; - private final static int T_LOWER_LETTER = 4; - private final static int T_LOWER_ROMAN = 2; - private final static int T_ORDINAL = 5; - private final static int T_UPPER_LETTER = 3; - private final static int T_UPPER_ROMAN = 1; + private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, + 50, 40, 10, 9, 5, 4, 1 }; + + private static final int T_ARABIC = 0; + private static final int T_LOWER_LETTER = 4; + private static final int T_LOWER_ROMAN = 2; + private static final int T_ORDINAL = 5; + private static final int T_UPPER_LETTER = 3; + private static final int T_UPPER_ROMAN = 1; public static String getNumber( int num, int style ) { switch ( style ) { case T_UPPER_ROMAN: - return C_ROMAN[num - 1].toUpperCase(); + return toRoman( num ).toUpperCase(); case T_LOWER_ROMAN: - return C_ROMAN[num - 1]; + return toRoman( num ); case T_UPPER_LETTER: - return C_LETTERS[num - 1].toUpperCase(); + return toLetters( num ).toUpperCase(); case T_LOWER_LETTER: - return C_LETTERS[num - 1]; + return toLetters( num ); case T_ARABIC: case T_ORDINAL: default: return String.valueOf( num ); } } + + private static String toLetters( int number ) + { + final int base = 26; + + if ( number <= 0 ) + throw new IllegalArgumentException( "Unsupported number: " + number ); + + if ( number < base + 1 ) + return ENGLISH_LETTERS[number - 1]; + + long toProcess = number; + + StringBuilder stringBuilder = new StringBuilder(); + int maxPower = 0; + { + int boundary = 0; + while ( toProcess > boundary ) + { + maxPower++; + boundary = boundary * base + base; + + if ( boundary > Integer.MAX_VALUE ) + throw new IllegalArgumentException( "Unsupported number: " + + toProcess ); + } + } + maxPower--; + + for ( int p = maxPower; p > 0; p-- ) + { + long boundary = 0; + long shift = 1; + for ( int i = 0; i < p; i++ ) + { + shift *= base; + boundary = boundary * base + base; + } + + int count = 0; + while ( toProcess > boundary ) + { + count++; + toProcess -= shift; + } + stringBuilder.append( ENGLISH_LETTERS[count - 1] ); + } + stringBuilder.append( ENGLISH_LETTERS[(int) toProcess - 1] ); + return stringBuilder.toString(); + } + + private static String toRoman( int number ) + { + if ( number <= 0 ) + throw new IllegalArgumentException( "Unsupported number: " + number ); + + StringBuilder result = new StringBuilder(); + + for ( int i = 0; i < ROMAN_LETTERS.length; i++ ) + { + String letter = ROMAN_LETTERS[i]; + int value = ROMAN_VALUES[i]; + while ( number >= value ) + { + number -= value; + result.append( letter ); + } + } + return result.toString(); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/converter/TestNumberFormatter.java b/src/scratchpad/testcases/org/apache/poi/hwpf/converter/TestNumberFormatter.java new file mode 100644 index 000000000..5cd60bfe5 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/converter/TestNumberFormatter.java @@ -0,0 +1,75 @@ +package org.apache.poi.hwpf.converter; + +import junit.framework.TestCase; + +public class TestNumberFormatter extends TestCase +{ + + public void testRoman() + { + assertEquals( "i", NumberFormatter.getNumber( 1, 2 ) ); + assertEquals( "ii", NumberFormatter.getNumber( 2, 2 ) ); + assertEquals( "iii", NumberFormatter.getNumber( 3, 2 ) ); + assertEquals( "iv", NumberFormatter.getNumber( 4, 2 ) ); + assertEquals( "v", NumberFormatter.getNumber( 5, 2 ) ); + assertEquals( "vi", NumberFormatter.getNumber( 6, 2 ) ); + assertEquals( "vii", NumberFormatter.getNumber( 7, 2 ) ); + assertEquals( "viii", NumberFormatter.getNumber( 8, 2 ) ); + assertEquals( "ix", NumberFormatter.getNumber( 9, 2 ) ); + assertEquals( "x", NumberFormatter.getNumber( 10, 2 ) ); + + assertEquals( "mdcvi", NumberFormatter.getNumber( 1606, 2 ) ); + assertEquals( "mcmx", NumberFormatter.getNumber( 1910, 2 ) ); + assertEquals( "mcmliv", NumberFormatter.getNumber( 1954, 2 ) ); + } + + public void testEnglish() + { + assertEquals( "a", NumberFormatter.getNumber( 1, 4 ) ); + assertEquals( "z", NumberFormatter.getNumber( 26, 4 ) ); + + assertEquals( "aa", NumberFormatter.getNumber( 1 * 26 + 1, 4 ) ); + assertEquals( "az", NumberFormatter.getNumber( 1 * 26 + 26, 4 ) ); + + assertEquals( "za", NumberFormatter.getNumber( 26 * 26 + 1, 4 ) ); + assertEquals( "zz", NumberFormatter.getNumber( 26 * 26 + 26, 4 ) ); + + assertEquals( "aaa", + NumberFormatter.getNumber( 26 * 26 + 1 * 26 + 1, 4 ) ); + assertEquals( "aaz", + NumberFormatter.getNumber( 26 * 26 + 1 * 26 + 26, 4 ) ); + + assertEquals( "aba", + NumberFormatter.getNumber( 1 * 26 * 26 + 2 * 26 + 1, 4 ) ); + assertEquals( "aza", + NumberFormatter.getNumber( 1 * 26 * 26 + 26 * 26 + 1, 4 ) ); + + assertEquals( "azz", + NumberFormatter.getNumber( 26 * 26 + 26 * 26 + 26, 4 ) ); + assertEquals( "baa", + NumberFormatter.getNumber( 2 * 26 * 26 + 1 * 26 + 1, 4 ) ); + assertEquals( "zaa", + NumberFormatter.getNumber( 26 * 26 * 26 + 1 * 26 + 1, 4 ) ); + assertEquals( "zzz", + NumberFormatter.getNumber( 26 * 26 * 26 + 26 * 26 + 26, 4 ) ); + + assertEquals( + "aaaa", + NumberFormatter.getNumber( 1 * 26 * 26 * 26 + 1 * 26 * 26 + 1 + * 26 + 1, 4 ) ); + assertEquals( + "azzz", + NumberFormatter.getNumber( 1 * 26 * 26 * 26 + 26 * 26 * 26 + 26 + * 26 + 26, 4 ) ); + assertEquals( + "zzzz", + NumberFormatter.getNumber( 26 * 26 * 26 * 26 + 26 * 26 * 26 + + 26 * 26 + 26, 4 ) ); + + for ( int i = 1; i < 1000000; i++ ) + { + // make sure there is no exceptions + NumberFormatter.getNumber( i, 4 ); + } + } +}