You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ye...@apache.org on 2010/05/10 18:11:51 UTC

svn commit: r942809 [3/3] - in /poi/trunk: ./ src/contrib/src/org/apache/poi/hssf/contrib/ src/documentation/content/xdocs/ src/examples/src/org/apache/poi/hssf/view/ src/examples/src/org/apache/poi/hssf/view/brush/ src/examples/src/org/apache/poi/ss/e...

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellFormatResult.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormatResult.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormatResult.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormatResult.java Mon May 10 16:11:50 2010
@@ -0,0 +1,58 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import java.awt.*;
+
+/**
+ * This object contains the result of applying a cell format or cell format part
+ * to a value.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ * @see CellFormatPart#apply(Object)
+ * @see CellFormat#apply(Object)
+ */
+public class CellFormatResult {
+    /**
+     * This is <tt>true</tt> if no condition was given that applied to the
+     * value, or if the condition is satisfied.  If a condition is relevant, and
+     * when applied the value fails the test, this is <tt>false</tt>.
+     */
+    public final boolean applies;
+
+    /** The resulting text.  This will never be <tt>null</tt>. */
+    public final String text;
+
+    /**
+     * The color the format sets, or <tt>null</tt> if the format sets no color.
+     * This will always be <tt>null</tt> if {@link #applies} is <tt>false</tt>.
+     */
+    public final Color textColor;
+
+    /**
+     * Creates a new format result object.
+     *
+     * @param applies   The value for {@link #applies}.
+     * @param text      The value for {@link #text}.
+     * @param textColor The value for {@link #textColor}.
+     */
+    public CellFormatResult(boolean applies, String text, Color textColor) {
+        this.applies = applies;
+        this.text = text;
+        this.textColor = (applies ? textColor : null);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellFormatType.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormatType.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormatType.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormatType.java Mon May 10 16:11:50 2010
@@ -0,0 +1,74 @@
+package org.apache.poi.ss.format;
+
+/**
+ * The different kinds of formats that the formatter understands.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public enum CellFormatType {
+
+    /** The general (default) format; also used for <tt>"General"</tt>. */
+    GENERAL {
+        CellFormatter formatter(String pattern) {
+            return new CellGeneralFormatter();
+        }
+        boolean isSpecial(char ch) {
+            return false;
+        }
+    },
+    /** A numeric format. */
+    NUMBER {
+        boolean isSpecial(char ch) {
+            return false;
+        }
+        CellFormatter formatter(String pattern) {
+            return new CellNumberFormatter(pattern);
+        }
+    },
+    /** A date format. */
+    DATE {
+        boolean isSpecial(char ch) {
+            return ch == '\'' || (ch <= '\u007f' && Character.isLetter(ch));
+        }
+        CellFormatter formatter(String pattern) {
+            return new CellDateFormatter(pattern);
+        }
+    },
+    /** An elapsed time format. */
+    ELAPSED {
+        boolean isSpecial(char ch) {
+            return false;
+        }
+        CellFormatter formatter(String pattern) {
+            return new CellElapsedFormatter(pattern);
+        }
+    },
+    /** A text format. */
+    TEXT {
+        boolean isSpecial(char ch) {
+            return false;
+        }
+        CellFormatter formatter(String pattern) {
+            return new CellTextFormatter(pattern);
+        }
+    };
+
+    /**
+     * Returns <tt>true</tt> if the format is special and needs to be quoted.
+     *
+     * @param ch The character to test.
+     *
+     * @return <tt>true</tt> if the format is special and needs to be quoted.
+     */
+    abstract boolean isSpecial(char ch);
+
+    /**
+     * Returns a new formatter of the appropriate type, for the given pattern.
+     * The pattern must be appropriate for the type.
+     *
+     * @param pattern The pattern to use.
+     *
+     * @return A new formatter of the appropriate type, for the given pattern.
+     */
+    abstract CellFormatter formatter(String pattern);
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,102 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import java.util.Locale;
+import java.util.logging.Logger;
+
+/**
+ * This is the abstract supertype for the various cell formatters.
+ *
+ * @@author Ken Arnold, Industrious Media LLC
+ */
+public abstract class CellFormatter {
+    /** The original specified format. */
+    protected final String format;
+
+    /**
+     * This is the locale used to get a consistent format result from which to
+     * work.
+     */
+    public static final Locale LOCALE = Locale.US;
+
+    /**
+     * Creates a new formatter object, storing the format in {@link #format}.
+     *
+     * @param format The format.
+     */
+    public CellFormatter(String format) {
+        this.format = format;
+    }
+
+    /** The logger to use in the formatting code. */
+    static final Logger logger = Logger.getLogger(
+            CellFormatter.class.getName());
+
+    /**
+     * Format a value according the format string.
+     *
+     * @param toAppendTo The buffer to append to.
+     * @param value      The value to format.
+     */
+    public abstract void formatValue(StringBuffer toAppendTo, Object value);
+
+    /**
+     * Format a value according to the type, in the most basic way.
+     *
+     * @param toAppendTo The buffer to append to.
+     * @param value      The value to format.
+     */
+    public abstract void simpleValue(StringBuffer toAppendTo, Object value);
+
+    /**
+     * Formats the value, returning the resulting string.
+     *
+     * @param value The value to format.
+     *
+     * @return The value, formatted.
+     */
+    public String format(Object value) {
+        StringBuffer sb = new StringBuffer();
+        formatValue(sb, value);
+        return sb.toString();
+    }
+
+    /**
+     * Formats the value in the most basic way, returning the resulting string.
+     *
+     * @param value The value to format.
+     *
+     * @return The value, formatted.
+     */
+    public String simpleFormat(Object value) {
+        StringBuffer sb = new StringBuffer();
+        simpleValue(sb, value);
+        return sb.toString();
+    }
+
+    /**
+     * Returns the input string, surrounded by quotes.
+     *
+     * @param str The string to quote.
+     *
+     * @return The input string, surrounded by quotes.
+     */
+    static String quote(String str) {
+        return '"' + str + '"';
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,84 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import java.util.Formatter;
+
+/**
+ * A formatter for the default "General" cell format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellGeneralFormatter extends CellFormatter {
+    /** Creates a new general formatter. */
+    public CellGeneralFormatter() {
+        super("General");
+    }
+
+    /**
+     * The general style is not quite the same as any other, or any combination
+     * of others.
+     *
+     * @param toAppendTo The buffer to append to.
+     * @param value      The value to format.
+     */
+    public void formatValue(StringBuffer toAppendTo, Object value) {
+        if (value instanceof Number) {
+            double val = ((Number) value).doubleValue();
+            if (val == 0) {
+                toAppendTo.append('0');
+                return;
+            }
+
+            String fmt;
+            double exp = Math.log10(Math.abs(val));
+            boolean stripZeros = true;
+            if (exp > 10 || exp < -9)
+                fmt = "%1.5E";
+            else if ((long) val != val)
+                fmt = "%1.9f";
+            else {
+                fmt = "%1.0f";
+                stripZeros = false;
+            }
+
+            Formatter formatter = new Formatter(toAppendTo);
+            formatter.format(LOCALE, fmt, value);
+            if (stripZeros) {
+                // strip off trailing zeros
+                int removeFrom;
+                if (fmt.endsWith("E"))
+                    removeFrom = toAppendTo.lastIndexOf("E") - 1;
+                else
+                    removeFrom = toAppendTo.length() - 1;
+                while (toAppendTo.charAt(removeFrom) == '0') {
+                    toAppendTo.deleteCharAt(removeFrom--);
+                }
+                if (toAppendTo.charAt(removeFrom) == '.') {
+                    toAppendTo.deleteCharAt(removeFrom--);
+                }
+            }
+        } else {
+            toAppendTo.append(value.toString());
+        }
+    }
+
+    /** Equivalent to {@link #formatValue(StringBuffer,Object)}. {@inheritDoc}. */
+    public void simpleValue(StringBuffer toAppendTo, Object value) {
+        formatValue(toAppendTo, value);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellNumberFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellNumberFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellNumberFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellNumberFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,1085 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import org.apache.poi.ss.format.CellFormatPart.PartHandler;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+
+/**
+ * This class implements printing out a value using a number format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellNumberFormatter extends CellFormatter {
+    private final String desc;
+    private String printfFmt;
+    private double scale;
+    private Special decimalPoint;
+    private Special slash;
+    private Special exponent;
+    private Special numerator;
+    private Special afterInteger;
+    private Special afterFractional;
+    private boolean integerCommas;
+    private final List<Special> specials;
+    private List<Special> integerSpecials;
+    private List<Special> fractionalSpecials;
+    private List<Special> numeratorSpecials;
+    private List<Special> denominatorSpecials;
+    private List<Special> exponentSpecials;
+    private List<Special> exponentDigitSpecials;
+    private int maxDenominator;
+    private String numeratorFmt;
+    private String denominatorFmt;
+    private boolean improperFraction;
+    private DecimalFormat decimalFmt;
+
+    static final CellFormatter SIMPLE_NUMBER = new CellFormatter("General") {
+        public void formatValue(StringBuffer toAppendTo, Object value) {
+            if (value == null)
+                return;
+            if (value instanceof Number) {
+                Number num = (Number) value;
+                if (num.doubleValue() % 1.0 == 0)
+                    SIMPLE_INT.formatValue(toAppendTo, value);
+                else
+                    SIMPLE_FLOAT.formatValue(toAppendTo, value);
+            } else {
+                CellTextFormatter.SIMPLE_TEXT.formatValue(toAppendTo, value);
+            }
+        }
+
+        public void simpleValue(StringBuffer toAppendTo, Object value) {
+            formatValue(toAppendTo, value);
+        }
+    };
+
+    private static final CellFormatter SIMPLE_INT = new CellNumberFormatter(
+            "#");
+    private static final CellFormatter SIMPLE_FLOAT = new CellNumberFormatter(
+            "#.#");
+
+    /**
+     * This class is used to mark where the special characters in the format
+     * are, as opposed to the other characters that are simply printed.
+     */
+    static class Special {
+        final char ch;
+        int pos;
+
+        Special(char ch, int pos) {
+            this.ch = ch;
+            this.pos = pos;
+        }
+
+        @Override
+        public String toString() {
+            return "'" + ch + "' @ " + pos;
+        }
+    }
+
+    /**
+     * This class represents a single modification to a result string.  The way
+     * this works is complicated, but so is numeric formatting.  In general, for
+     * most formats, we use a DecimalFormat object that will put the string out
+     * in a known format, usually with all possible leading and trailing zeros.
+     * We then walk through the result and the orginal format, and note any
+     * modifications that need to be made.  Finally, we go through and apply
+     * them all, dealing with overlapping modifications.
+     */
+    static class StringMod implements Comparable<StringMod> {
+        final Special special;
+        final int op;
+        CharSequence toAdd;
+        Special end;
+        boolean startInclusive;
+        boolean endInclusive;
+
+        public static final int BEFORE = 1;
+        public static final int AFTER = 2;
+        public static final int REPLACE = 3;
+
+        private StringMod(Special special, CharSequence toAdd, int op) {
+            this.special = special;
+            this.toAdd = toAdd;
+            this.op = op;
+        }
+
+        public StringMod(Special start, boolean startInclusive, Special end,
+                boolean endInclusive, char toAdd) {
+            this(start, startInclusive, end, endInclusive);
+            this.toAdd = toAdd + "";
+        }
+
+        public StringMod(Special start, boolean startInclusive, Special end,
+                boolean endInclusive) {
+            special = start;
+            this.startInclusive = startInclusive;
+            this.end = end;
+            this.endInclusive = endInclusive;
+            op = REPLACE;
+            toAdd = "";
+        }
+
+        public int compareTo(StringMod that) {
+            int diff = special.pos - that.special.pos;
+            if (diff != 0)
+                return diff;
+            else
+                return op - that.op;
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            try {
+                return compareTo((StringMod) that) == 0;
+            } catch (RuntimeException ignored) {
+                // NullPointerException or CastException
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return special.hashCode() + op;
+        }
+    }
+
+    private class NumPartHandler implements PartHandler {
+        private char insertSignForExponent;
+
+        public String handlePart(Matcher m, String part, CellFormatType type,
+                StringBuffer desc) {
+            int pos = desc.length();
+            char firstCh = part.charAt(0);
+            switch (firstCh) {
+            case 'e':
+            case 'E':
+                // See comment in writeScientific -- exponent handling is complex.
+                // (1) When parsing the format, remove the sign from after the 'e' and
+                // put it before the first digit of the exponent.
+                if (exponent == null && specials.size() > 0) {
+                    specials.add(exponent = new Special('.', pos));
+                    insertSignForExponent = part.charAt(1);
+                    return part.substring(0, 1);
+                }
+                break;
+
+            case '0':
+            case '?':
+            case '#':
+                if (insertSignForExponent != '\0') {
+                    specials.add(new Special(insertSignForExponent, pos));
+                    desc.append(insertSignForExponent);
+                    insertSignForExponent = '\0';
+                    pos++;
+                }
+                for (int i = 0; i < part.length(); i++) {
+                    char ch = part.charAt(i);
+                    specials.add(new Special(ch, pos + i));
+                }
+                break;
+
+            case '.':
+                if (decimalPoint == null && specials.size() > 0)
+                    specials.add(decimalPoint = new Special('.', pos));
+                break;
+
+            case '/':
+                //!! This assumes there is a numerator and a denominator, but these are actually optional
+                if (slash == null && specials.size() > 0) {
+                    numerator = previousNumber();
+                    // If the first number in the whole format is the numerator, the
+                    // entire number should be printed as an improper fraction
+                    if (numerator == firstDigit(specials))
+                        improperFraction = true;
+                    specials.add(slash = new Special('.', pos));
+                }
+                break;
+
+            case '%':
+                // don't need to remember because we don't need to do anything with these
+                scale *= 100;
+                break;
+
+            default:
+                return null;
+            }
+            return part;
+        }
+    }
+
+    /**
+     * Creates a new cell number formatter.
+     *
+     * @param format The format to parse.
+     */
+    public CellNumberFormatter(String format) {
+        super(format);
+
+        scale = 1;
+
+        specials = new LinkedList<Special>();
+
+        NumPartHandler partHandler = new NumPartHandler();
+        StringBuffer descBuf = CellFormatPart.parseFormat(format,
+                CellFormatType.NUMBER, partHandler);
+
+        // These are inconsistent settings, so ditch 'em
+        if ((decimalPoint != null || exponent != null) && slash != null) {
+            slash = null;
+            numerator = null;
+        }
+
+        interpretCommas(descBuf);
+
+        int precision;
+        int fractionPartWidth = 0;
+        if (decimalPoint == null) {
+            precision = 0;
+        } else {
+            precision = interpretPrecision();
+            fractionPartWidth = 1 + precision;
+            if (precision == 0) {
+                // This means the format has a ".", but that output should have no decimals after it.
+                // We just stop treating it specially
+                specials.remove(decimalPoint);
+                decimalPoint = null;
+            }
+        }
+
+        if (precision == 0)
+            fractionalSpecials = Collections.emptyList();
+        else
+            fractionalSpecials = specials.subList(specials.indexOf(
+                    decimalPoint) + 1, fractionalEnd());
+        if (exponent == null)
+            exponentSpecials = Collections.emptyList();
+        else {
+            int exponentPos = specials.indexOf(exponent);
+            exponentSpecials = specialsFor(exponentPos, 2);
+            exponentDigitSpecials = specialsFor(exponentPos + 2);
+        }
+
+        if (slash == null) {
+            numeratorSpecials = Collections.emptyList();
+            denominatorSpecials = Collections.emptyList();
+        } else {
+            if (numerator == null)
+                numeratorSpecials = Collections.emptyList();
+            else
+                numeratorSpecials = specialsFor(specials.indexOf(numerator));
+
+            denominatorSpecials = specialsFor(specials.indexOf(slash) + 1);
+            if (denominatorSpecials.isEmpty()) {
+                // no denominator follows the slash, drop the fraction idea
+                numeratorSpecials = Collections.emptyList();
+            } else {
+                maxDenominator = maxValue(denominatorSpecials);
+                numeratorFmt = singleNumberFormat(numeratorSpecials);
+                denominatorFmt = singleNumberFormat(denominatorSpecials);
+            }
+        }
+
+        integerSpecials = specials.subList(0, integerEnd());
+
+        if (exponent == null) {
+            StringBuffer fmtBuf = new StringBuffer("%");
+
+            int integerPartWidth = calculateIntegerPartWidth();
+            int totalWidth = integerPartWidth + fractionPartWidth;
+
+            fmtBuf.append('0').append(totalWidth).append('.').append(precision);
+
+            fmtBuf.append("f");
+            printfFmt = fmtBuf.toString();
+        } else {
+            StringBuffer fmtBuf = new StringBuffer();
+            boolean first = true;
+            List<Special> specialList = integerSpecials;
+            if (integerSpecials.size() == 1) {
+                // If we don't do this, we get ".6e5" instead of "6e4"
+                fmtBuf.append("0");
+                first = false;
+            } else
+                for (Special s : specialList) {
+                    if (isDigitFmt(s)) {
+                        fmtBuf.append(first ? '#' : '0');
+                        first = false;
+                    }
+                }
+            if (fractionalSpecials.size() > 0) {
+                fmtBuf.append('.');
+                for (Special s : fractionalSpecials) {
+                    if (isDigitFmt(s)) {
+                        if (!first)
+                            fmtBuf.append('0');
+                        first = false;
+                    }
+                }
+            }
+            fmtBuf.append('E');
+            placeZeros(fmtBuf, exponentSpecials.subList(2,
+                    exponentSpecials.size()));
+            decimalFmt = new DecimalFormat(fmtBuf.toString());
+        }
+
+        if (exponent != null)
+            scale =
+                    1;  // in "e" formats,% and trailing commas have no scaling effect
+
+        desc = descBuf.toString();
+    }
+
+    private static void placeZeros(StringBuffer sb, List<Special> specials) {
+        for (Special s : specials) {
+            if (isDigitFmt(s))
+                sb.append('0');
+        }
+    }
+
+    private static Special firstDigit(List<Special> specials) {
+        for (Special s : specials) {
+            if (isDigitFmt(s))
+                return s;
+        }
+        return null;
+    }
+
+    static StringMod insertMod(Special special, CharSequence toAdd, int where) {
+        return new StringMod(special, toAdd, where);
+    }
+
+    static StringMod deleteMod(Special start, boolean startInclusive,
+            Special end, boolean endInclusive) {
+
+        return new StringMod(start, startInclusive, end, endInclusive);
+    }
+
+    static StringMod replaceMod(Special start, boolean startInclusive,
+            Special end, boolean endInclusive, char withChar) {
+
+        return new StringMod(start, startInclusive, end, endInclusive,
+                withChar);
+    }
+
+    private static String singleNumberFormat(List<Special> numSpecials) {
+        return "%0" + numSpecials.size() + "d";
+    }
+
+    private static int maxValue(List<Special> s) {
+        return (int) Math.round(Math.pow(10, s.size()) - 1);
+    }
+
+    private List<Special> specialsFor(int pos, int takeFirst) {
+        if (pos >= specials.size())
+            return Collections.emptyList();
+        ListIterator<Special> it = specials.listIterator(pos + takeFirst);
+        Special last = it.next();
+        int end = pos + takeFirst;
+        while (it.hasNext()) {
+            Special s = it.next();
+            if (!isDigitFmt(s) || s.pos - last.pos > 1)
+                break;
+            end++;
+            last = s;
+        }
+        return specials.subList(pos, end + 1);
+    }
+
+    private List<Special> specialsFor(int pos) {
+        return specialsFor(pos, 0);
+    }
+
+    private static boolean isDigitFmt(Special s) {
+        return s.ch == '0' || s.ch == '?' || s.ch == '#';
+    }
+
+    private Special previousNumber() {
+        ListIterator<Special> it = specials.listIterator(specials.size());
+        while (it.hasPrevious()) {
+            Special s = it.previous();
+            if (isDigitFmt(s)) {
+                Special numStart = s;
+                Special last = s;
+                while (it.hasPrevious()) {
+                    s = it.previous();
+                    if (last.pos - s.pos > 1) // it has to be continuous digits
+                        break;
+                    if (isDigitFmt(s))
+                        numStart = s;
+                    else
+                        break;
+                    last = s;
+                }
+                return numStart;
+            }
+        }
+        return null;
+    }
+
+    private int calculateIntegerPartWidth() {
+        ListIterator<Special> it = specials.listIterator();
+        int digitCount = 0;
+        while (it.hasNext()) {
+            Special s = it.next();
+            //!! Handle fractions: The previous set of digits before that is the numerator, so we should stop short of that
+            if (s == afterInteger)
+                break;
+            else if (isDigitFmt(s))
+                digitCount++;
+        }
+        return digitCount;
+    }
+
+    private int interpretPrecision() {
+        if (decimalPoint == null) {
+            return -1;
+        } else {
+            int precision = 0;
+            ListIterator<Special> it = specials.listIterator(specials.indexOf(
+                    decimalPoint));
+            if (it.hasNext())
+                it.next();  // skip over the decimal point itself
+            while (it.hasNext()) {
+                Special s = it.next();
+                if (isDigitFmt(s))
+                    precision++;
+                else
+                    break;
+            }
+            return precision;
+        }
+    }
+
+    private void interpretCommas(StringBuffer sb) {
+        // In the integer part, commas at the end are scaling commas; other commas mean to show thousand-grouping commas
+        ListIterator<Special> it = specials.listIterator(integerEnd());
+
+        boolean stillScaling = true;
+        integerCommas = false;
+        while (it.hasPrevious()) {
+            Special s = it.previous();
+            if (s.ch != ',') {
+                stillScaling = false;
+            } else {
+                if (stillScaling) {
+                    scale /= 1000;
+                } else {
+                    integerCommas = true;
+                }
+            }
+        }
+
+        if (decimalPoint != null) {
+            it = specials.listIterator(fractionalEnd());
+            while (it.hasPrevious()) {
+                Special s = it.previous();
+                if (s.ch != ',') {
+                    break;
+                } else {
+                    scale /= 1000;
+                }
+            }
+        }
+
+        // Now strip them out -- we only need their interpretation, not their presence
+        it = specials.listIterator();
+        int removed = 0;
+        while (it.hasNext()) {
+            Special s = it.next();
+            s.pos -= removed;
+            if (s.ch == ',') {
+                removed++;
+                it.remove();
+                sb.deleteCharAt(s.pos);
+            }
+        }
+    }
+
+    private int integerEnd() {
+        if (decimalPoint != null)
+            afterInteger = decimalPoint;
+        else if (exponent != null)
+            afterInteger = exponent;
+        else if (numerator != null)
+            afterInteger = numerator;
+        else
+            afterInteger = null;
+        return afterInteger == null ? specials.size() : specials.indexOf(
+                afterInteger);
+    }
+
+    private int fractionalEnd() {
+        int end;
+        if (exponent != null)
+            afterFractional = exponent;
+        else if (numerator != null)
+            afterInteger = numerator;
+        else
+            afterFractional = null;
+        end = afterFractional == null ? specials.size() : specials.indexOf(
+                afterFractional);
+        return end;
+    }
+
+    /** {@inheritDoc} */
+    public void formatValue(StringBuffer toAppendTo, Object valueObject) {
+        double value = ((Number) valueObject).doubleValue();
+        value *= scale;
+
+        // the '-' sign goes at the front, always, so we pick it out
+        boolean negative = value < 0;
+        if (negative)
+            value = -value;
+
+        // Split out the fractional part if we need to print a fraction
+        double fractional = 0;
+        if (slash != null) {
+            if (improperFraction) {
+                fractional = value;
+                value = 0;
+            } else {
+                fractional = value % 1.0;
+                //noinspection SillyAssignment
+                value = (long) value;
+            }
+        }
+
+        Set<StringMod> mods = new TreeSet<StringMod>();
+        StringBuffer output = new StringBuffer(desc);
+
+        if (exponent != null) {
+            writeScientific(value, output, mods);
+        } else if (improperFraction) {
+            writeFraction(value, null, fractional, output, mods);
+        } else {
+            StringBuffer result = new StringBuffer();
+            Formatter f = new Formatter(result);
+            f.format(LOCALE, printfFmt, value);
+
+            if (numerator == null) {
+                writeFractional(result, output);
+                writeInteger(result, output, integerSpecials, mods,
+                        integerCommas);
+            } else {
+                writeFraction(value, result, fractional, output, mods);
+            }
+        }
+
+        // Now strip out any remaining '#'s and add any pending text ...
+        ListIterator<Special> it = specials.listIterator();
+        Iterator<StringMod> changes = mods.iterator();
+        StringMod nextChange = (changes.hasNext() ? changes.next() : null);
+        int adjust = 0;
+        BitSet deletedChars = new BitSet(); // records chars already deleted
+        while (it.hasNext()) {
+            Special s = it.next();
+            int adjustedPos = s.pos + adjust;
+            if (!deletedChars.get(s.pos) && output.charAt(adjustedPos) == '#') {
+                output.deleteCharAt(adjustedPos);
+                adjust--;
+                deletedChars.set(s.pos);
+            }
+            while (nextChange != null && s == nextChange.special) {
+                int lenBefore = output.length();
+                int modPos = s.pos + adjust;
+                int posTweak = 0;
+                switch (nextChange.op) {
+                case StringMod.AFTER:
+                    // ignore adding a comma after a deleted char (which was a '#')
+                    if (nextChange.toAdd.equals(",") && deletedChars.get(s.pos))
+                        break;
+                    posTweak = 1;
+                    //noinspection fallthrough
+                case StringMod.BEFORE:
+                    output.insert(modPos + posTweak, nextChange.toAdd);
+                    break;
+
+                case StringMod.REPLACE:
+                    int delPos =
+                            s.pos; // delete starting pos in original coordinates
+                    if (!nextChange.startInclusive) {
+                        delPos++;
+                        modPos++;
+                    }
+
+                    // Skip over anything already deleted
+                    while (deletedChars.get(delPos)) {
+                        delPos++;
+                        modPos++;
+                    }
+
+                    int delEndPos =
+                            nextChange.end.pos; // delete end point in original
+                    if (nextChange.endInclusive)
+                        delEndPos++;
+
+                    int modEndPos =
+                            delEndPos + adjust; // delete end point in current
+
+                    if (modPos < modEndPos) {
+                        if (nextChange.toAdd == "")
+                            output.delete(modPos, modEndPos);
+                        else {
+                            char fillCh = nextChange.toAdd.charAt(0);
+                            for (int i = modPos; i < modEndPos; i++)
+                                output.setCharAt(i, fillCh);
+                        }
+                        deletedChars.set(delPos, delEndPos);
+                    }
+                    break;
+
+                default:
+                    throw new IllegalStateException(
+                            "Unknown op: " + nextChange.op);
+                }
+                adjust += output.length() - lenBefore;
+
+                if (changes.hasNext())
+                    nextChange = changes.next();
+                else
+                    nextChange = null;
+            }
+        }
+
+        // Finally, add it to the string
+        if (negative)
+            toAppendTo.append('-');
+        toAppendTo.append(output);
+    }
+
+    private void writeScientific(double value, StringBuffer output,
+            Set<StringMod> mods) {
+
+        StringBuffer result = new StringBuffer();
+        FieldPosition fractionPos = new FieldPosition(
+                DecimalFormat.FRACTION_FIELD);
+        decimalFmt.format(value, result, fractionPos);
+        writeInteger(result, output, integerSpecials, mods, integerCommas);
+        writeFractional(result, output);
+
+        /*
+        * Exponent sign handling is complex.
+        *
+        * In DecimalFormat, you never put the sign in the format, and the sign only
+        * comes out of the format if it is negative.
+        *
+        * In Excel, you always say whether to always show the sign ("e+") or only
+        * show negative signs ("e-").
+        *
+        * Also in Excel, where you put the sign in the format is NOT where it comes
+        * out in the result.  In the format, the sign goes with the "e"; in the
+        * output it goes with the exponent value.  That is, if you say "#e-|#" you
+        * get "1e|-5", not "1e-|5". This makes sense I suppose, but it complicates
+        * things.
+        *
+        * Finally, everything else in this formatting code assumes that the base of
+        * the result is the original format, and that starting from that situation,
+        * the indexes of the original special characters can be used to place the new
+        * characters.  As just described, this is not true for the exponent's sign.
+        * <p/>
+        * So here is how we handle it:
+        *
+        * (1) When parsing the format, remove the sign from after the 'e' and put it
+        * before the first digit of the exponent (where it will be shown).
+        *
+        * (2) Determine the result's sign.
+        *
+        * (3) If it's missing, put the sign into the output to keep the result
+        * lined up with the output. (In the result, "after the 'e'" and "before the
+        * first digit" are the same because the result has no extra chars to be in
+        * the way.)
+        *
+        * (4) In the output, remove the sign if it should not be shown ("e-" was used
+        * and the sign is negative) or set it to the correct value.
+        */
+
+        // (2) Determine the result's sign.
+        int ePos = fractionPos.getEndIndex();
+        int signPos = ePos + 1;
+        char expSignRes = result.charAt(signPos);
+        if (expSignRes != '-') {
+            // not a sign, so it's a digit, and therefore a positive exponent
+            expSignRes = '+';
+            // (3) If it's missing, put the sign into the output to keep the result
+            // lined up with the output.
+            result.insert(signPos, '+');
+        }
+
+        // Now the result lines up like it is supposed to with the specials' indexes
+        ListIterator<Special> it = exponentSpecials.listIterator(1);
+        Special expSign = it.next();
+        char expSignFmt = expSign.ch;
+
+        // (4) In the output, remove the sign if it should not be shown or set it to
+        // the correct value.
+        if (expSignRes == '-' || expSignFmt == '+')
+            mods.add(replaceMod(expSign, true, expSign, true, expSignRes));
+        else
+            mods.add(deleteMod(expSign, true, expSign, true));
+
+        StringBuffer exponentNum = new StringBuffer(result.substring(
+                signPos + 1));
+        writeInteger(exponentNum, output, exponentDigitSpecials, mods, false);
+    }
+
+    private void writeFraction(double value, StringBuffer result,
+            double fractional, StringBuffer output, Set<StringMod> mods) {
+
+        // Figure out if we are to suppress either the integer or fractional part.
+        // With # the suppressed part is removed; with ? it is replaced with spaces.
+        if (!improperFraction) {
+            // If fractional part is zero, and numerator doesn't have '0', write out
+            // only the integer part and strip the rest.
+            if (fractional == 0 && !hasChar('0', numeratorSpecials)) {
+                writeInteger(result, output, integerSpecials, mods, false);
+
+                Special start = integerSpecials.get(integerSpecials.size() - 1);
+                Special end = denominatorSpecials.get(
+                        denominatorSpecials.size() - 1);
+                if (hasChar('?', integerSpecials, numeratorSpecials,
+                        denominatorSpecials)) {
+                    //if any format has '?', then replace the fraction with spaces
+                    mods.add(replaceMod(start, false, end, true, ' '));
+                } else {
+                    // otherwise, remove the fraction
+                    mods.add(deleteMod(start, false, end, true));
+                }
+
+                // That's all, just return
+                return;
+            } else {
+                // New we check to see if we should remove the integer part
+                boolean allZero = (value == 0 && fractional == 0);
+                boolean willShowFraction = fractional != 0 || hasChar('0',
+                        numeratorSpecials);
+                boolean removeBecauseZero = allZero && (hasOnly('#',
+                        integerSpecials) || !hasChar('0', numeratorSpecials));
+                boolean removeBecauseFraction =
+                        !allZero && value == 0 && willShowFraction && !hasChar(
+                                '0', integerSpecials);
+                if (removeBecauseZero || removeBecauseFraction) {
+                    Special start = integerSpecials.get(
+                            integerSpecials.size() - 1);
+                    if (hasChar('?', integerSpecials, numeratorSpecials)) {
+                        mods.add(replaceMod(start, true, numerator, false,
+                                ' '));
+                    } else {
+                        mods.add(deleteMod(start, true, numerator, false));
+                    }
+                } else {
+                    // Not removing the integer part -- print it out
+                    writeInteger(result, output, integerSpecials, mods, false);
+                }
+            }
+        }
+
+        // Calculate and print the actual fraction (improper or otherwise)
+        try {
+            int n;
+            int d;
+            // the "fractional % 1" captures integer values in improper fractions
+            if (fractional == 0 || (improperFraction && fractional % 1 == 0)) {
+                // 0 as a fraction is reported by excel as 0/1
+                n = (int) Math.round(fractional);
+                d = 1;
+            } else {
+                Fraction frac = new Fraction(fractional, maxDenominator);
+                n = frac.getNumerator();
+                d = frac.getDenominator();
+            }
+            if (improperFraction)
+                n += Math.round(value * d);
+            writeSingleInteger(numeratorFmt, n, output, numeratorSpecials,
+                    mods);
+            writeSingleInteger(denominatorFmt, d, output, denominatorSpecials,
+                    mods);
+        } catch (RuntimeException ignored) {
+            ignored.printStackTrace();
+        }
+    }
+
+    private static boolean hasChar(char ch, List<Special>... numSpecials) {
+        for (List<Special> specials : numSpecials) {
+            for (Special s : specials) {
+                if (s.ch == ch) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasOnly(char ch, List<Special>... numSpecials) {
+        for (List<Special> specials : numSpecials) {
+            for (Special s : specials) {
+                if (s.ch != ch) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void writeSingleInteger(String fmt, int num, StringBuffer output,
+            List<Special> numSpecials, Set<StringMod> mods) {
+
+        StringBuffer sb = new StringBuffer();
+        Formatter formatter = new Formatter(sb);
+        formatter.format(LOCALE, fmt, num);
+        writeInteger(sb, output, numSpecials, mods, false);
+    }
+
+    private void writeInteger(StringBuffer result, StringBuffer output,
+            List<Special> numSpecials, Set<StringMod> mods,
+            boolean showCommas) {
+
+        int pos = result.indexOf(".") - 1;
+        if (pos < 0) {
+            if (exponent != null && numSpecials == integerSpecials)
+                pos = result.indexOf("E") - 1;
+            else
+                pos = result.length() - 1;
+        }
+
+        int strip;
+        for (strip = 0; strip < pos; strip++) {
+            char resultCh = result.charAt(strip);
+            if (resultCh != '0' && resultCh != ',')
+                break;
+        }
+
+        ListIterator<Special> it = numSpecials.listIterator(numSpecials.size());
+        boolean followWithComma = false;
+        Special lastOutputIntegerDigit = null;
+        int digit = 0;
+        while (it.hasPrevious()) {
+            char resultCh;
+            if (pos >= 0)
+                resultCh = result.charAt(pos);
+            else {
+                // If result is shorter than field, pretend there are leading zeros
+                resultCh = '0';
+            }
+            Special s = it.previous();
+            followWithComma = showCommas && digit > 0 && digit % 3 == 0;
+            boolean zeroStrip = false;
+            if (resultCh != '0' || s.ch == '0' || s.ch == '?' || pos >= strip) {
+                zeroStrip = s.ch == '?' && pos < strip;
+                output.setCharAt(s.pos, (zeroStrip ? ' ' : resultCh));
+                lastOutputIntegerDigit = s;
+            }
+            if (followWithComma) {
+                mods.add(insertMod(s, zeroStrip ? " " : ",", StringMod.AFTER));
+                followWithComma = false;
+            }
+            digit++;
+            --pos;
+        }
+        StringBuffer extraLeadingDigits = new StringBuffer();
+        if (pos >= 0) {
+            // We ran out of places to put digits before we ran out of digits; put this aside so we can add it later
+            ++pos;  // pos was decremented at the end of the loop above when the iterator was at its end
+            extraLeadingDigits = new StringBuffer(result.substring(0, pos));
+            if (showCommas) {
+                while (pos > 0) {
+                    if (digit > 0 && digit % 3 == 0)
+                        extraLeadingDigits.insert(pos, ',');
+                    digit++;
+                    --pos;
+                }
+            }
+            mods.add(insertMod(lastOutputIntegerDigit, extraLeadingDigits,
+                    StringMod.BEFORE));
+        }
+    }
+
+    private void writeFractional(StringBuffer result, StringBuffer output) {
+        int digit;
+        int strip;
+        ListIterator<Special> it;
+        if (fractionalSpecials.size() > 0) {
+            digit = result.indexOf(".") + 1;
+            if (exponent != null)
+                strip = result.indexOf("e") - 1;
+            else
+                strip = result.length() - 1;
+            while (strip > digit && result.charAt(strip) == '0')
+                strip--;
+            it = fractionalSpecials.listIterator();
+            while (it.hasNext()) {
+                Special s = it.next();
+                char resultCh = result.charAt(digit);
+                if (resultCh != '0' || s.ch == '0' || digit < strip)
+                    output.setCharAt(s.pos, resultCh);
+                else if (s.ch == '?') {
+                    // This is when we're in trailing zeros, and the format is '?'.  We still strip out remaining '#'s later
+                    output.setCharAt(s.pos, ' ');
+                }
+                digit++;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * For a number, this is <tt>"#"</tt> for integer values, and <tt>"#.#"</tt>
+     * for floating-point values.
+     */
+    public void simpleValue(StringBuffer toAppendTo, Object value) {
+        SIMPLE_NUMBER.formatValue(toAppendTo, value);
+    }
+
+    /**
+     *  Based on org.apache.commons.math.fraction.Fraction from Apache Commons-Math.
+     *  YK: The only reason of having this inner class is to avoid dependency on the Commons-Math jar.
+     */
+    private static class Fraction {
+        /** The denominator. */
+        private final int denominator;
+
+        /** The numerator. */
+        private final int numerator;
+
+        /**
+         * Create a fraction given the double value and either the maximum error
+         * allowed or the maximum number of denominator digits.
+         *
+         * @param value the double value to convert to a fraction.
+         * @param epsilon maximum error allowed.  The resulting fraction is within
+         *        <code>epsilon</code> of <code>value</code>, in absolute terms.
+         * @param maxDenominator maximum denominator value allowed.
+         * @param maxIterations maximum number of convergents
+         * @throws RuntimeException if the continued fraction failed to
+         *         converge.
+         */
+        private Fraction(double value, double epsilon, int maxDenominator, int maxIterations)
+        {
+            long overflow = Integer.MAX_VALUE;
+            double r0 = value;
+            long a0 = (long)Math.floor(r0);
+            if (a0 > overflow) {
+                throw new IllegalArgumentException("Overflow trying to convert "+value+" to fraction ("+a0+"/"+1l+")");
+            }
+
+            // check for (almost) integer arguments, which should not go
+            // to iterations.
+            if (Math.abs(a0 - value) < epsilon) {
+                this.numerator = (int) a0;
+                this.denominator = 1;
+                return;
+            }
+
+            long p0 = 1;
+            long q0 = 0;
+            long p1 = a0;
+            long q1 = 1;
+
+            long p2;
+            long q2;
+
+            int n = 0;
+            boolean stop = false;
+            do {
+                ++n;
+                double r1 = 1.0 / (r0 - a0);
+                long a1 = (long)Math.floor(r1);
+                p2 = (a1 * p1) + p0;
+                q2 = (a1 * q1) + q0;
+                if ((p2 > overflow) || (q2 > overflow)) {
+                    throw new RuntimeException("Overflow trying to convert "+value+" to fraction ("+p2+"/"+q2+")");
+                }
+
+                double convergent = (double)p2 / (double)q2;
+                if (n < maxIterations && Math.abs(convergent - value) > epsilon && q2 < maxDenominator) {
+                    p0 = p1;
+                    p1 = p2;
+                    q0 = q1;
+                    q1 = q2;
+                    a0 = a1;
+                    r0 = r1;
+                } else {
+                    stop = true;
+                }
+            } while (!stop);
+
+            if (n >= maxIterations) {
+                throw new RuntimeException("Unable to convert "+value+" to fraction after "+maxIterations+" iterations");
+            }
+
+            if (q2 < maxDenominator) {
+                this.numerator = (int) p2;
+                this.denominator = (int) q2;
+            } else {
+                this.numerator = (int) p1;
+                this.denominator = (int) q1;
+            }
+
+        }
+
+        /**
+         * Create a fraction given the double value and maximum denominator.
+         * <p>
+         * References:
+         * <ul>
+         * <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">
+         * Continued Fraction</a> equations (11) and (22)-(26)</li>
+         * </ul>
+         * </p>
+         * @param value the double value to convert to a fraction.
+         * @param maxDenominator The maximum allowed value for denominator
+         * @throws RuntimeException if the continued fraction failed to
+         *         converge
+         */
+        public Fraction(double value, int maxDenominator)
+        {
+           this(value, 0, maxDenominator, 100);
+        }
+
+        /**
+         * Access the denominator.
+         * @return the denominator.
+         */
+        public int getDenominator() {
+            return denominator;
+        }
+
+        /**
+         * Access the numerator.
+         * @return the numerator.
+         */
+        public int getNumerator() {
+            return numerator;
+        }
+
+    }
+
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellTextFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellTextFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellTextFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellTextFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,79 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import org.apache.poi.ss.format.CellFormatPart.PartHandler;
+
+import java.util.regex.Matcher;
+
+/**
+ * This class implements printing out text.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellTextFormatter extends CellFormatter {
+    private final int[] textPos;
+    private final String desc;
+
+    static final CellFormatter SIMPLE_TEXT = new CellTextFormatter("@");
+
+    public CellTextFormatter(String format) {
+        super(format);
+
+        final int[] numPlaces = new int[1];
+
+        desc = CellFormatPart.parseFormat(format, CellFormatType.TEXT,
+                new PartHandler() {
+                    public String handlePart(Matcher m, String part,
+                            CellFormatType type, StringBuffer desc) {
+                        if (part.equals("@")) {
+                            numPlaces[0]++;
+                            return "\u0000";
+                        }
+                        return null;
+                    }
+                }).toString();
+
+        // Remember the "@" positions in last-to-first order (to make insertion easier)
+        textPos = new int[numPlaces[0]];
+        int pos = desc.length() - 1;
+        for (int i = 0; i < textPos.length; i++) {
+            textPos[i] = desc.lastIndexOf("\u0000", pos);
+            pos = textPos[i] - 1;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void formatValue(StringBuffer toAppendTo, Object obj) {
+        int start = toAppendTo.length();
+        String text = obj.toString();
+        toAppendTo.append(desc);
+        for (int i = 0; i < textPos.length; i++) {
+            int pos = start + textPos[i];
+            toAppendTo.replace(pos, pos + 1, text);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * For text, this is just printing the text.
+     */
+    public void simpleValue(StringBuffer toAppendTo, Object value) {
+        SIMPLE_TEXT.formatValue(toAppendTo, value);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/package.html
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/package.html?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/package.html (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/package.html Mon May 10 16:11:50 2010
@@ -0,0 +1,3 @@
+<body>
+This package contains classes that implement cell formatting
+</body>

Added: poi/trunk/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java (added)
+++ poi/trunk/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java Mon May 10 16:11:50 2010
@@ -0,0 +1,126 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.xssf.XSSFITestDataProvider;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Test the individual CellFormatPart types. */
+public class TestCellFormatPart extends CellFormatTestBase {
+    private static final Pattern NUMBER_EXTRACT_FMT = Pattern.compile(
+            "([-+]?[0-9]+)(\\.[0-9]+)?.*(?:(e).*?([+-]?[0-9]+))",
+            Pattern.CASE_INSENSITIVE);
+
+    public TestCellFormatPart() {
+        super(XSSFITestDataProvider.instance);
+    }
+
+    public void testGeneralFormat() throws Exception {
+        runFormatTests("GeneralFormatTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                int type = CellFormat.ultimateType(cell);
+                if (type == Cell.CELL_TYPE_BOOLEAN)
+                    return cell.getBooleanCellValue() ? "TRUE" : "FALSE";
+                else if (type == Cell.CELL_TYPE_NUMERIC)
+                    return cell.getNumericCellValue();
+                else
+                    return cell.getStringCellValue();
+            }
+        });
+    }
+
+    public void testNumberFormat() throws Exception {
+        runFormatTests("NumberFormatTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                return cell.getNumericCellValue();
+            }
+        });
+    }
+
+    public void testNumberApproxFormat() throws Exception {
+        runFormatTests("NumberFormatApproxTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                return cell.getNumericCellValue();
+            }
+
+            @Override
+            void equivalent(String expected, String actual,
+                    CellFormatPart format) {
+                double expectedVal = extractNumber(expected);
+                double actualVal = extractNumber(actual);
+                // equal within 1%
+                double delta = expectedVal / 100;
+                assertEquals("format \"" + format + "\"," + expected + " ~= " +
+                        actual, expectedVal, actualVal, delta);
+            }
+        });
+    }
+
+    public void testDateFormat() throws Exception {
+        runFormatTests("DateFormatTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                return cell.getDateCellValue();
+            }
+        });
+    }
+
+    public void testElapsedFormat() throws Exception {
+        runFormatTests("ElapsedFormatTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                return cell.getNumericCellValue();
+            }
+        });
+    }
+
+    public void testTextFormat() throws Exception {
+        runFormatTests("TextFormatTests.xlsx", new CellValue() {
+            public Object getValue(Cell cell) {
+                if (CellFormat.ultimateType(cell) == Cell.CELL_TYPE_BOOLEAN)
+                    return cell.getBooleanCellValue() ? "TRUE" : "FALSE";
+                else
+                    return cell.getStringCellValue();
+            }
+        });
+    }
+
+    public void testConditions() throws Exception {
+        runFormatTests("FormatConditionTests.xlsx", new CellValue() {
+            Object getValue(Cell cell) {
+                return cell.getNumericCellValue();
+            }
+        });
+    }
+
+    private double extractNumber(String str) {
+        Matcher m = NUMBER_EXTRACT_FMT.matcher(str);
+        if (!m.find())
+            throw new IllegalArgumentException(
+                    "Cannot find numer in \"" + str + "\"");
+
+        StringBuffer sb = new StringBuffer();
+        // The groups in the pattern are the parts of the number
+        for (int i = 1; i <= m.groupCount(); i++) {
+            String part = m.group(i);
+            if (part != null)
+                sb.append(part);
+        }
+        return Double.valueOf(sb.toString());
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java Mon May 10 16:11:50 2010
@@ -0,0 +1,293 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import junit.framework.TestCase;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.ITestDataProvider;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.*;
+
+import static java.awt.Color.*;
+import java.io.IOException;
+
+/**
+ * This class is a base class for spreadsheet-based tests, such as are used for
+ * cell formatting.  This reads tests from the spreadsheet, as well as reading
+ * flags that can be used to paramterize these tests.
+ * <p/>
+ * Each test has four parts: The expected result (column A), the format string
+ * (column B), the value to format (column C), and a comma-separated list of
+ * categores that this test falls in. Normally all tests are run, but if the
+ * flag "Categories" is not empty, only tests that have at least one category
+ * listed in "Categories" are run.
+ */
+@SuppressWarnings(
+        {"JUnitTestCaseWithNoTests", "JUnitTestClassNamingConvention"})
+public class CellFormatTestBase extends TestCase {
+    private final ITestDataProvider _testDataProvider;
+
+    protected Workbook workbook;
+
+    private String testFile;
+    private Map<String, String> testFlags;
+    private boolean tryAllColors;
+    private JLabel label;
+
+    private static final String[] COLOR_NAMES =
+            {"Black", "Red", "Green", "Blue", "Yellow", "Cyan", "Magenta",
+                    "White"};
+    private static final Color[] COLORS =
+            {BLACK, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA, WHITE};
+
+    public static final Color TEST_COLOR = ORANGE.darker();
+
+    protected CellFormatTestBase(ITestDataProvider testDataProvider) {
+        _testDataProvider = testDataProvider;
+    }
+
+    abstract static class CellValue {
+        abstract Object getValue(Cell cell);
+
+        @SuppressWarnings({"UnusedDeclaration"})
+        Color getColor(Cell cell) {
+            return TEST_COLOR;
+        }
+
+        void equivalent(String expected, String actual, CellFormatPart format) {
+            assertEquals("format \"" + format + "\"", '"' + expected + '"',
+                    '"' + actual + '"');
+        }
+    }
+
+    protected void runFormatTests(String workbookName, CellValue valueGetter)
+            throws IOException {
+
+        openWorkbook(workbookName);
+
+        readFlags(workbook);
+
+        Set<String> runCategories = new TreeSet<String>(
+                String.CASE_INSENSITIVE_ORDER);
+        String runCategoryList = flagString("Categories", "");
+        if (runCategoryList != null) {
+            runCategories.addAll(Arrays.asList(runCategoryList.split(
+                    "\\s*,\\s*")));
+            runCategories.remove(""); // this can be found and means nothing
+        }
+
+        Sheet sheet = workbook.getSheet("Tests");
+        int end = sheet.getLastRowNum();
+        // Skip the header row, therefore "+ 1"
+        for (int r = sheet.getFirstRowNum() + 1; r <= end; r++) {
+            Row row = sheet.getRow(r);
+            if (row == null)
+                continue;
+            int cellnum = 0;
+            String expectedText = row.getCell(cellnum).getStringCellValue();
+            String format = row.getCell(1).getStringCellValue();
+            String testCategoryList = row.getCell(3).getStringCellValue();
+            boolean byCategory = runByCategory(runCategories, testCategoryList);
+            if ((!expectedText.isEmpty() || !format.isEmpty()) && byCategory) {
+                Cell cell = row.getCell(2);
+                tryFormat(r, expectedText, format, valueGetter, cell);
+            }
+        }
+    }
+
+    /**
+     * Open a given workbook.
+     *
+     * @param workbookName The workbook name.  This is presumed to live in the
+     *                     "spreadsheets" directory under the directory named in
+     *                     the Java property "POI.testdata.path".
+     *
+     * @throws IOException
+     */
+    protected void openWorkbook(String workbookName)
+            throws IOException {
+        workbook = _testDataProvider.openSampleWorkbook(workbookName);
+        workbook.setMissingCellPolicy(Row.CREATE_NULL_AS_BLANK);
+        testFile = workbookName;
+    }
+
+    /**
+     * Read the flags from the workbook.  Flags are on the sheet named "Flags",
+     * and consist of names in column A and values in column B.  These are put
+     * into a map that can be queried later.
+     *
+     * @param wb The workbook to look in.
+     */
+    private void readFlags(Workbook wb) {
+        Sheet flagSheet = wb.getSheet("Flags");
+        testFlags = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+        if (flagSheet != null) {
+            int end = flagSheet.getLastRowNum();
+            // Skip the header row, therefore "+ 1"
+            for (int r = flagSheet.getFirstRowNum() + 1; r <= end; r++) {
+                Row row = flagSheet.getRow(r);
+                if (row == null)
+                    continue;
+                String flagName = row.getCell(0).getStringCellValue();
+                String flagValue = row.getCell(1).getStringCellValue();
+                if (flagName.length() > 0) {
+                    testFlags.put(flagName, flagValue);
+                }
+            }
+        }
+
+        tryAllColors = flagBoolean("AllColors", true);
+    }
+
+    /**
+     * Returns <tt>true</tt> if any of the categories for this run are contained
+     * in the test's listed categories.
+     *
+     * @param categories     The categories of tests to be run.  If this is
+     *                       empty, then all tests will be run.
+     * @param testCategories The categories that this test is in.  This is a
+     *                       comma-separated list.  If <em>any</em> tests in
+     *                       this list are in <tt>categories</tt>, the test will
+     *                       be run.
+     *
+     * @return <tt>true</tt> if the test should be run.
+     */
+    private boolean runByCategory(Set<String> categories,
+            String testCategories) {
+
+        if (categories.isEmpty())
+            return true;
+        // If there are specified categories, find out if this has one of them
+        for (String category : testCategories.split("\\s*,\\s*")) {
+            if (categories.contains(category)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void tryFormat(int row, String expectedText, String desc,
+            CellValue getter, Cell cell) {
+
+        Object value = getter.getValue(cell);
+        Color testColor = getter.getColor(cell);
+        if (testColor == null)
+            testColor = TEST_COLOR;
+
+        if (label == null)
+            label = new JLabel();
+        label.setForeground(testColor);
+        label.setText("xyzzy");
+
+        System.out.printf("Row %d: \"%s\" -> \"%s\": expected \"%s\"", row + 1,
+                String.valueOf(value), desc, expectedText);
+        System.out.flush();
+        String actualText = tryColor(desc, null, getter, value, expectedText,
+                testColor);
+        System.out.printf(", actual \"%s\")%n", actualText);
+        System.out.flush();
+
+        if (tryAllColors && testColor != TEST_COLOR) {
+            for (int i = 0; i < COLOR_NAMES.length; i++) {
+                String cname = COLOR_NAMES[i];
+                tryColor(desc, cname, getter, value, expectedText, COLORS[i]);
+            }
+        }
+    }
+
+    private String tryColor(String desc, String cname, CellValue getter,
+            Object value, String expectedText, Color expectedColor) {
+
+        if (cname != null)
+            desc = "[" + cname + "]" + desc;
+        Color origColor = label.getForeground();
+        CellFormatPart format = new CellFormatPart(desc);
+        if (!format.apply(label, value).applies) {
+            // If this doesn't apply, no color change is expected
+            expectedColor = origColor;
+        }
+
+        String actualText = label.getText();
+        Color actualColor = label.getForeground();
+        getter.equivalent(expectedText, actualText, format);
+        assertEquals(cname == null ? "no color" : "color " + cname,
+                expectedColor, actualColor);
+        return actualText;
+    }
+
+    /**
+     * Returns the value for the given flag.  The flag has the value of
+     * <tt>true</tt> if the text value is <tt>"true"</tt>, <tt>"yes"</tt>, or
+     * <tt>"on"</tt> (ignoring case).
+     *
+     * @param flagName The name of the flag to fetch.
+     * @param expected The value for the flag that is expected when the tests
+     *                 are run for a full test.  If the current value is not the
+     *                 expected one, you will get a warning in the test output.
+     *                 This is so that you do not accidentally leave a flag set
+     *                 to a value that prevents running some tests, thereby
+     *                 letting you accidentally release code that is not fully
+     *                 tested.
+     *
+     * @return The value for the flag.
+     */
+    protected boolean flagBoolean(String flagName, boolean expected) {
+        String value = testFlags.get(flagName);
+        boolean isSet;
+        if (value == null)
+            isSet = false;
+        else {
+            isSet = value.equalsIgnoreCase("true") || value.equalsIgnoreCase(
+                    "yes") || value.equalsIgnoreCase("on");
+        }
+        warnIfUnexpected(flagName, expected, isSet);
+        return isSet;
+    }
+
+    /**
+     * Returns the value for the given flag.
+     *
+     * @param flagName The name of the flag to fetch.
+     * @param expected The value for the flag that is expected when the tests
+     *                 are run for a full test.  If the current value is not the
+     *                 expected one, you will get a warning in the test output.
+     *                 This is so that you do not accidentally leave a flag set
+     *                 to a value that prevents running some tests, thereby
+     *                 letting you accidentally release code that is not fully
+     *                 tested.
+     *
+     * @return The value for the flag.
+     */
+    protected String flagString(String flagName, String expected) {
+        String value = testFlags.get(flagName);
+        if (value == null)
+            value = "";
+        warnIfUnexpected(flagName, expected, value);
+        return value;
+    }
+
+    private void warnIfUnexpected(String flagName, Object expected,
+            Object actual) {
+        if (!actual.equals(expected)) {
+            System.err.println(
+                    "WARNING: " + testFile + ": " + "Flag " + flagName +
+                            " = \"" + actual + "\" [not \"" + expected + "\"]");
+        }
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormat.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormat.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormat.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormat.java Mon May 10 16:11:50 2010
@@ -0,0 +1,32 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import org.apache.poi.ss.format.CellFormat;
+
+import javax.swing.*;
+
+import junit.framework.TestCase;
+
+public class TestCellFormat extends TestCase {
+    public void testSome() {
+        JLabel l = new JLabel();
+        CellFormat fmt = CellFormat.getInstance(
+                "\"$\"#,##0.00_);[Red]\\(\"$\"#,##0.00\\)");
+        fmt.apply(l, 1.1);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java Mon May 10 16:11:50 2010
@@ -0,0 +1,64 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.format;
+
+import junit.framework.TestCase;
+import org.apache.poi.ss.format.CellFormatCondition;
+
+public class TestCellFormatCondition extends TestCase {
+    public void testSVConditions() {
+        CellFormatCondition lt = CellFormatCondition.getInstance("<", "1.5");
+        assertTrue(lt.pass(1.4));
+        assertFalse(lt.pass(1.5));
+        assertFalse(lt.pass(1.6));
+
+        CellFormatCondition le = CellFormatCondition.getInstance("<=", "1.5");
+        assertTrue(le.pass(1.4));
+        assertTrue(le.pass(1.5));
+        assertFalse(le.pass(1.6));
+
+        CellFormatCondition gt = CellFormatCondition.getInstance(">", "1.5");
+        assertFalse(gt.pass(1.4));
+        assertFalse(gt.pass(1.5));
+        assertTrue(gt.pass(1.6));
+
+        CellFormatCondition ge = CellFormatCondition.getInstance(">=", "1.5");
+        assertFalse(ge.pass(1.4));
+        assertTrue(ge.pass(1.5));
+        assertTrue(ge.pass(1.6));
+
+        CellFormatCondition eqs = CellFormatCondition.getInstance("=", "1.5");
+        assertFalse(eqs.pass(1.4));
+        assertTrue(eqs.pass(1.5));
+        assertFalse(eqs.pass(1.6));
+
+        CellFormatCondition eql = CellFormatCondition.getInstance("==", "1.5");
+        assertFalse(eql.pass(1.4));
+        assertTrue(eql.pass(1.5));
+        assertFalse(eql.pass(1.6));
+
+        CellFormatCondition neo = CellFormatCondition.getInstance("<>", "1.5");
+        assertTrue(neo.pass(1.4));
+        assertFalse(neo.pass(1.5));
+        assertTrue(neo.pass(1.6));
+
+        CellFormatCondition nen = CellFormatCondition.getInstance("!=", "1.5");
+        assertTrue(nen.pass(1.4));
+        assertFalse(nen.pass(1.5));
+        assertTrue(nen.pass(1.6));
+    }
+}
\ No newline at end of file

Added: poi/trunk/test-data/spreadsheet/DateFormatTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/DateFormatTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/DateFormatTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/ElapsedFormatTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/ElapsedFormatTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/ElapsedFormatTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/FormatChoiceTests.xls
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/FormatChoiceTests.xls?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/FormatChoiceTests.xls
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/FormatChoiceTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/FormatChoiceTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/FormatChoiceTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/FormatConditionTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/FormatConditionTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/FormatConditionTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/GeneralFormatTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/GeneralFormatTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/GeneralFormatTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/NumberFormatApproxTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/NumberFormatApproxTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/NumberFormatApproxTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/NumberFormatTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/NumberFormatTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/NumberFormatTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/TextFormatTests.xlsx
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/TextFormatTests.xlsx?rev=942809&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/TextFormatTests.xlsx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org