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 [2/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/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java Mon May 10 16:11:50 2010
@@ -0,0 +1,62 @@
+/* ====================================================================
+   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.hssf.view.brush;
+
+import java.awt.*;
+
+/**
+ * This Stroke implementation applies a BasicStroke to a shape twice. If you
+ * draw with this Stroke, then instead of outlining the shape, you're outlining
+ * the outline of the shape.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class DoubleStroke implements Brush {
+    BasicStroke stroke1, stroke2; // the two strokes to use
+
+    /**
+     * Creates a new double-stroke brush.  This surrounds a cell with a two
+     * lines separated by white space between.
+     *
+     * @param width1 The width of the blank space in the middle
+     * @param width2 The width of the each of the two drawn strokes.
+     */
+    public DoubleStroke(float width1, float width2) {
+        stroke1 = new BasicStroke(width1); // Constructor arguments specify
+        stroke2 = new BasicStroke(width2); // the line widths for the strokes
+    }
+
+    /**
+     * Stroke the outline.
+     *
+     * @param s The shape in which to stroke.
+     *
+     * @return The created stroke as a new shape.
+     */
+    public Shape createStrokedShape(Shape s) {
+        // Use the first stroke to create an outline of the shape
+        Shape outline = stroke1.createStrokedShape(s);
+        // Use the second stroke to create an outline of that outline.
+        // It is this outline of the outline that will be filled in
+        return stroke2.createStrokedShape(outline);
+    }
+
+    /** {@inheritDoc} */
+    public float getLineWidth() {
+        return stroke1.getLineWidth() + 2 * stroke2.getLineWidth();
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java Mon May 10 16:11:50 2010
@@ -0,0 +1,178 @@
+/* ====================================================================
+   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.hssf.view.brush;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to hold pending brush paintings.  The model is that some
+ * border drawing requires drawing strokes after all the cells have been
+ * painted. The list of pending paintings can be put in this object during the
+ * initial paint of the component, and then executed at the appropriate time,
+ * such as at the end of the containing object's {@link
+ * JComponent#paintChildren(Graphics)} method.
+ * <p/>
+ * It is up to the parent component to invoke the {@link #paint(Graphics2D)}
+ * method of this objet at that appropriate time.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class PendingPaintings {
+    /**
+     * The name of the client property that holds this object in the parent
+     * component.
+     */
+    public static final String PENDING_PAINTINGS =
+            PendingPaintings.class.getSimpleName();
+
+    private final List<Painting> paintings;
+
+    /** A single painting description. */
+    public static class Painting {
+        final Stroke stroke;
+        final Color color;
+        final Shape shape;
+        final AffineTransform transform;
+
+        /**
+         * Creates a new painting description.
+         *
+         * @param stroke    The stroke to paint.
+         * @param color     The color of the stroke.
+         * @param shape     The shape of the stroke.
+         * @param transform The transformation matrix to use.
+         */
+        public Painting(Stroke stroke, Color color, Shape shape,
+                AffineTransform transform) {
+
+            this.color = color;
+            this.shape = shape;
+            this.stroke = stroke;
+            this.transform = transform;
+        }
+
+        /**
+         * Draw the painting.
+         *
+         * @param g The graphics object to use to draw with.
+         */
+        public void draw(Graphics2D g) {
+            g.setTransform(transform);
+            g.setStroke(stroke);
+            g.setColor(color);
+            g.draw(shape);
+        }
+    }
+
+    /**
+     * Creates a new object on the given parent.  The created object will be
+     * stored as a client property.
+     *
+     * @param parent
+     */
+    public PendingPaintings(JComponent parent) {
+        paintings = new ArrayList<Painting>();
+        parent.putClientProperty(PENDING_PAINTINGS, this);
+    }
+
+    /** Drops all pending paintings. */
+    public void clear() {
+        paintings.clear();
+    }
+
+    /**
+     * Paints all pending paintings.  Once they have been painted they are
+     * removed from the list of pending paintings (they aren't pending anymore,
+     * after all).
+     *
+     * @param g The graphics object to draw with.
+     */
+    public void paint(Graphics2D g) {
+        g.setBackground(Color.CYAN);
+        AffineTransform origTransform = g.getTransform();
+        for (Painting c : paintings) {
+            c.draw(g);
+        }
+        g.setTransform(origTransform);
+
+        clear();
+    }
+
+    /**
+     * Adds a new pending painting to the list on the given component.  This
+     * will find the first ancestor that has a {@link PendingPaintings} client
+     * property, starting with the component itself.
+     *
+     * @param c      The component for which the painting is being added.
+     * @param g      The graphics object to draw with.
+     * @param stroke The stroke to draw.
+     * @param color  The color to draw with.
+     * @param shape  The shape to stroke.
+     */
+    public static void add(JComponent c, Graphics2D g, Stroke stroke,
+            Color color, Shape shape) {
+
+        add(c, new Painting(stroke, color, shape, g.getTransform()));
+    }
+
+    /**
+     * Adds a new pending painting to the list on the given component.  This
+     * will find the first ancestor that has a {@link PendingPaintings} client
+     * property, starting with the component itself.
+     *
+     * @param c           The component for which the painting is being added.
+     * @param newPainting The new painting.
+     */
+    public static void add(JComponent c, Painting newPainting) {
+        PendingPaintings pending = pendingPaintingsFor(c);
+        if (pending != null) {
+            pending.paintings.add(newPainting);
+        }
+    }
+
+    /**
+     * Returns the pending painting object for the given component, if any. This
+     * is retrieved from the first object found that has a {@link
+     * #PENDING_PAINTINGS} client property, starting with this component and
+     * looking up its ancestors (parent, parent's parent, etc.)
+     * <p/>
+     * This allows any descendant of a component that has a {@link
+     * PendingPaintings} property to add its own pending paintings.
+     *
+     * @param c The component for which the painting is being added.
+     *
+     * @return The pending painting object for that component, or <tt>null</tt>
+     *         if there is none.
+     */
+    public static PendingPaintings pendingPaintingsFor(JComponent c) {
+        for (Component parent = c;
+             parent != null;
+             parent = parent.getParent()) {
+            if (parent instanceof JComponent) {
+                JComponent jc = (JComponent) parent;
+                Object pd = jc.getClientProperty(PENDING_PAINTINGS);
+                if (pd != null)
+                    return (PendingPaintings) pd;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html (added)
+++ poi/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html Mon May 10 16:11:50 2010
@@ -0,0 +1,4 @@
+This package contains some brushes that are used when drawing borders for Excel
+cells.
+
+@author Ken Arnold, Industrious Media LLC
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java Mon May 10 16:11:50 2010
@@ -0,0 +1,66 @@
+/* ====================================================================
+   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.examples.html;
+
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFPalette;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Formatter;
+
+/**
+ * Implementation of {@link HtmlHelper} for HSSF files.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class HSSFHtmlHelper implements HtmlHelper {
+    private final HSSFWorkbook wb;
+    private final HSSFPalette colors;
+
+    private static final HSSFColor HSSF_AUTO = new HSSFColor.AUTOMATIC();
+
+    public HSSFHtmlHelper(HSSFWorkbook wb) {
+        this.wb = wb;
+        // If there is no custom palette, then this creates a new one that is
+        // a copy of the default
+        colors = wb.getCustomPalette();
+    }
+
+    public void colorStyles(CellStyle style, Formatter out) {
+        HSSFCellStyle cs = (HSSFCellStyle) style;
+        out.format("  /* fill pattern = %d */%n", cs.getFillPattern());
+        styleColor(out, "background-color", cs.getFillForegroundColor());
+        styleColor(out, "color", cs.getFont(wb).getColor());
+        styleColor(out, "border-left-color", cs.getLeftBorderColor());
+        styleColor(out, "border-right-color", cs.getRightBorderColor());
+        styleColor(out, "border-top-color", cs.getTopBorderColor());
+        styleColor(out, "border-bottom-color", cs.getBottomBorderColor());
+    }
+
+    private void styleColor(Formatter out, String attr, short index) {
+        HSSFColor color = colors.getColor(index);
+        if (index == HSSF_AUTO.getIndex() || color == null) {
+            out.format("  /* %s: index = %d */%n", attr, index);
+        } else {
+            short[] rgb = color.getTriplet();
+            out.format("  %s: #%02x%02x%02x; /* index = %d */%n", attr, rgb[0],
+                    rgb[1], rgb[2], index);
+        }
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java Mon May 10 16:11:50 2010
@@ -0,0 +1,23 @@
+package org.apache.poi.ss.examples.html;
+
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Formatter;
+
+/**
+ * This interface is used where code wants to be independent of the workbook
+ * formats.  If you are writing such code, you can add a method to this
+ * interface, and then implement it for both HSSF and XSSF workbooks, letting
+ * the driving code stay independent of format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public interface HtmlHelper {
+    /**
+     * Outputs the appropriate CSS style for the given cell style.
+     *
+     * @param style The cell style.
+     * @param out   The place to write the output.
+     */
+    void colorStyles(CellStyle style, Formatter out);
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java Mon May 10 16:11:50 2010
@@ -0,0 +1,443 @@
+/* ====================================================================
+   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.examples.html;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.format.CellFormat;
+import org.apache.poi.ss.format.CellFormatResult;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.poi.ss.usermodel.CellStyle.*;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+
+/**
+ * This example shows how to display a spreadsheet in HTML using the classes for
+ * spreadsheet display.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class ToHtml {
+    private final Workbook wb;
+    private final Appendable output;
+    private boolean completeHTML;
+    private Formatter out;
+    private boolean gotBounds;
+    private int firstColumn;
+    private int endColumn;
+    private HtmlHelper helper;
+
+    private static final String DEFAULTS_CLASS = "excelDefaults";
+    private static final String COL_HEAD_CLASS = "colHeader";
+    private static final String ROW_HEAD_CLASS = "rowHeader";
+
+    private static final Map<Short, String> ALIGN = mapFor(ALIGN_LEFT, "left",
+            ALIGN_CENTER, "center", ALIGN_RIGHT, "right", ALIGN_FILL, "left",
+            ALIGN_JUSTIFY, "left", ALIGN_CENTER_SELECTION, "center");
+
+    private static final Map<Short, String> VERTICAL_ALIGN = mapFor(
+            VERTICAL_BOTTOM, "bottom", VERTICAL_CENTER, "middle", VERTICAL_TOP,
+            "top");
+
+    private static final Map<Short, String> BORDER = mapFor(BORDER_DASH_DOT,
+            "dashed 1pt", BORDER_DASH_DOT_DOT, "dashed 1pt", BORDER_DASHED,
+            "dashed 1pt", BORDER_DOTTED, "dotted 1pt", BORDER_DOUBLE,
+            "double 3pt", BORDER_HAIR, "solid 1px", BORDER_MEDIUM, "solid 2pt",
+            BORDER_MEDIUM_DASH_DOT, "dashed 2pt", BORDER_MEDIUM_DASH_DOT_DOT,
+            "dashed 2pt", BORDER_MEDIUM_DASHED, "dashed 2pt", BORDER_NONE,
+            "none", BORDER_SLANTED_DASH_DOT, "dashed 2pt", BORDER_THICK,
+            "solid 3pt", BORDER_THIN, "dashed 1pt");
+
+    @SuppressWarnings({"unchecked"})
+    private static <K, V> Map<K, V> mapFor(Object... mapping) {
+        Map<K, V> map = new HashMap<K, V>();
+        for (int i = 0; i < mapping.length; i += 2) {
+            map.put((K) mapping[i], (V) mapping[i + 1]);
+        }
+        return map;
+    }
+
+    /**
+     * Creates a new converter to HTML for the given workbook.
+     *
+     * @param wb     The workbook.
+     * @param output Where the HTML output will be written.
+     *
+     * @return An object for converting the workbook to HTML.
+     */
+    public static ToHtml create(Workbook wb, Appendable output) {
+        return new ToHtml(wb, output);
+    }
+
+    /**
+     * Creates a new converter to HTML for the given workbook.  If the path ends
+     * with "<tt>.xlsx</tt>" an {@link XSSFWorkbook} will be used; otherwise
+     * this will use an {@link HSSFWorkbook}.
+     *
+     * @param path   The file that has the workbook.
+     * @param output Where the HTML output will be written.
+     *
+     * @return An object for converting the workbook to HTML.
+     */
+    public static ToHtml create(String path, Appendable output)
+            throws IOException {
+        return create(new FileInputStream(path), output);
+    }
+
+    /**
+     * Creates a new converter to HTML for the given workbook.  This attempts to
+     * detect whether the input is XML (so it should create an {@link
+     * XSSFWorkbook} or not (so it should create an {@link HSSFWorkbook}).
+     *
+     * @param in     The input stream that has the workbook.
+     * @param output Where the HTML output will be written.
+     *
+     * @return An object for converting the workbook to HTML.
+     */
+    public static ToHtml create(InputStream in, Appendable output)
+            throws IOException {
+        try {
+            Workbook wb = WorkbookFactory.create(in);
+            return create(wb, output);
+        } catch (InvalidFormatException e){
+            throw new IllegalArgumentException("Cannot create workbook from stream", e);
+        }
+    }
+
+    private ToHtml(Workbook wb, Appendable output) {
+        if (wb == null)
+            throw new NullPointerException("wb");
+        if (output == null)
+            throw new NullPointerException("output");
+        this.wb = wb;
+        this.output = output;
+        setupColorMap();
+    }
+
+    private void setupColorMap() {
+        if (wb instanceof HSSFWorkbook)
+            helper = new HSSFHtmlHelper((HSSFWorkbook) wb);
+        else if (wb instanceof XSSFWorkbook)
+            helper = new XSSFHtmlHelper((XSSFWorkbook) wb);
+        else
+            throw new IllegalArgumentException(
+                    "unknown workbook type: " + wb.getClass().getSimpleName());
+    }
+
+    /**
+     * Run this class as a program
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception Exception we don't recover from.
+     */
+    public static void main(String[] args) throws Exception {
+        if(args.length < 2){
+            System.err.println("usage: ToHtml inputWorkbook outputHtmlFile");
+            return;
+        }
+
+        ToHtml toHtml = create(args[0], new PrintWriter(new FileWriter(args[1])));
+        toHtml.setCompleteHTML(true);
+        toHtml.printPage();
+    }
+
+    public void setCompleteHTML(boolean completeHTML) {
+        this.completeHTML = completeHTML;
+    }
+
+    public void printPage() throws IOException {
+        try {
+            ensureOut();
+            if (completeHTML) {
+                out.format(
+                        "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>%n");
+                out.format("<html>%n");
+                out.format("<head>%n");
+                out.format("</head>%n");
+                out.format("<body>%n");
+            }
+
+            print();
+
+            if (completeHTML) {
+                out.format("</body>%n");
+                out.format("</html>%n");
+            }
+        } finally {
+            if (out != null)
+                out.close();
+            if (output instanceof Closeable) {
+                Closeable closeable = (Closeable) output;
+                closeable.close();
+            }
+        }
+    }
+
+    public void print() {
+        printInlineStyle();
+        printSheets();
+    }
+
+    private void printInlineStyle() {
+        //out.format("<link href=\"excelStyle.css\" rel=\"stylesheet\" type=\"text/css\">%n");
+        out.format("<style type=\"text/css\">%n");
+        printStyles();
+        out.format("</style>%n");
+    }
+
+    private void ensureOut() {
+        if (out == null)
+            out = new Formatter(output);
+    }
+
+    public void printStyles() {
+        ensureOut();
+
+        // First, copy the base css
+        BufferedReader in = null;
+        try {
+            in = new BufferedReader(new InputStreamReader(
+                    getClass().getResourceAsStream("excelStyle.css")));
+            String line;
+            while ((line = in.readLine()) != null) {
+                out.format("%s%n", line);
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException("Reading standard css", e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    //noinspection ThrowFromFinallyBlock
+                    throw new IllegalStateException("Reading standard css", e);
+                }
+            }
+        }
+
+        // now add css for each used style
+        Set<CellStyle> seen = new HashSet<CellStyle>();
+        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+            Sheet sheet = wb.getSheetAt(i);
+            Iterator<Row> rows = sheet.rowIterator();
+            while (rows.hasNext()) {
+                Row row = rows.next();
+                for (Cell cell : row) {
+                    CellStyle style = cell.getCellStyle();
+                    if (!seen.contains(style)) {
+                        printStyle(style);
+                        seen.add(style);
+                    }
+                }
+            }
+        }
+    }
+
+    private void printStyle(CellStyle style) {
+        out.format(".%s .%s {%n", DEFAULTS_CLASS, styleName(style));
+        styleContents(style);
+        out.format("}%n");
+    }
+
+    private void styleContents(CellStyle style) {
+        styleOut("text-align", style.getAlignment(), ALIGN);
+        styleOut("vertical-align", style.getAlignment(), VERTICAL_ALIGN);
+        fontStyle(style);
+        borderStyles(style);
+        helper.colorStyles(style, out);
+    }
+
+    private void borderStyles(CellStyle style) {
+        styleOut("border-left", style.getBorderLeft(), BORDER);
+        styleOut("border-right", style.getBorderRight(), BORDER);
+        styleOut("border-top", style.getBorderTop(), BORDER);
+        styleOut("border-bottom", style.getBorderBottom(), BORDER);
+    }
+
+    private void fontStyle(CellStyle style) {
+        Font font = wb.getFontAt(style.getFontIndex());
+
+        if (font.getBoldweight() >= HSSFFont.BOLDWEIGHT_NORMAL)
+            out.format("  font-weight: bold;%n");
+        if (font.getItalic())
+            out.format("  font-style: italic;%n");
+
+        int fontheight = font.getFontHeightInPoints();
+        if (fontheight == 9) {
+            //fix for stupid ol Windows
+            fontheight = 10;
+        }
+        out.format("  font-size: %dpt;%n", fontheight);
+
+        // Font color is handled with the other colors
+    }
+
+    private String styleName(CellStyle style) {
+        if (style == null)
+            style = wb.getCellStyleAt((short) 0);
+        StringBuilder sb = new StringBuilder();
+        Formatter fmt = new Formatter(sb);
+        fmt.format("style_%02x", style.getIndex());
+        return fmt.toString();
+    }
+
+    private <K> void styleOut(String attr, K key, Map<K, String> mapping) {
+        String value = mapping.get(key);
+        if (value != null) {
+            out.format("  %s: %s;%n", attr, value);
+        }
+    }
+
+    private static int ultimateCellType(Cell c) {
+        int type = c.getCellType();
+        if (type == Cell.CELL_TYPE_FORMULA)
+            type = c.getCachedFormulaResultType();
+        return type;
+    }
+
+    private void printSheets() {
+        ensureOut();
+        Sheet sheet = wb.getSheetAt(0);
+        printSheet(sheet);
+    }
+
+    public void printSheet(Sheet sheet) {
+        ensureOut();
+        out.format("<table class=%s>%n", DEFAULTS_CLASS);
+        printCols(sheet);
+        printSheetContent(sheet);
+        out.format("</table>%n");
+    }
+
+    private void printCols(Sheet sheet) {
+        out.format("<col/>%n");
+        ensureColumnBounds(sheet);
+        for (int i = firstColumn; i < endColumn; i++) {
+            out.format("<col/>%n");
+        }
+    }
+
+    private void ensureColumnBounds(Sheet sheet) {
+        if (gotBounds)
+            return;
+
+        Iterator<Row> iter = sheet.rowIterator();
+        firstColumn = (iter.hasNext() ? Integer.MAX_VALUE : 0);
+        endColumn = 0;
+        while (iter.hasNext()) {
+            Row row = iter.next();
+            short firstCell = row.getFirstCellNum();
+            if (firstCell >= 0) {
+                firstColumn = Math.min(firstColumn, firstCell);
+                endColumn = Math.max(endColumn, row.getLastCellNum());
+            }
+        }
+        gotBounds = true;
+    }
+
+    private void printColumnHeads() {
+        out.format("<thead>%n");
+        out.format("  <tr class=%s>%n", COL_HEAD_CLASS);
+        out.format("    <th class=%s>&#x25CA;</th>%n", COL_HEAD_CLASS);
+        //noinspection UnusedDeclaration
+        StringBuilder colName = new StringBuilder();
+        for (int i = firstColumn; i < endColumn; i++) {
+            colName.setLength(0);
+            int cnum = i;
+            do {
+                colName.insert(0, (char) ('A' + cnum % 26));
+                cnum /= 26;
+            } while (cnum > 0);
+            out.format("    <th class=%s>%s</th>%n", COL_HEAD_CLASS, colName);
+        }
+        out.format("  </tr>%n");
+        out.format("</thead>%n");
+    }
+
+    private void printSheetContent(Sheet sheet) {
+        printColumnHeads();
+
+        out.format("<tbody>%n");
+        Iterator<Row> rows = sheet.rowIterator();
+        while (rows.hasNext()) {
+            Row row = rows.next();
+
+            out.format("  <tr>%n");
+            out.format("    <td class=%s>%d</td>%n", ROW_HEAD_CLASS,
+                    row.getRowNum() + 1);
+            for (int i = firstColumn; i < endColumn; i++) {
+                String content = "&nbsp;";
+                String attrs = "";
+                CellStyle style = null;
+                if (i >= row.getFirstCellNum() && i < row.getLastCellNum()) {
+                    Cell cell = row.getCell(i);
+                    if (cell != null) {
+                        style = cell.getCellStyle();
+                        attrs = tagStyle(cell, style);
+                        //Set the value that is rendered for the cell
+                        //also applies the format
+                        CellFormat cf = CellFormat.getInstance(
+                                style.getDataFormatString());
+                        CellFormatResult result = cf.apply(cell);
+                        content = result.text;
+                        if (content.equals(""))
+                            content = "&nbsp;";
+                    }
+                }
+                out.format("    <td class=%s %s>%s</td>%n", styleName(style),
+                        attrs, content);
+            }
+            out.format("  </tr>%n");
+        }
+        out.format("</tbody>%n");
+    }
+
+    private String tagStyle(Cell cell, CellStyle style) {
+        if (style.getAlignment() == ALIGN_GENERAL) {
+            switch (ultimateCellType(cell)) {
+            case HSSFCell.CELL_TYPE_STRING:
+                return "style=\"text-align: left;\"";
+            case HSSFCell.CELL_TYPE_BOOLEAN:
+            case HSSFCell.CELL_TYPE_ERROR:
+                return "style=\"text-align: center;\"";
+            case HSSFCell.CELL_TYPE_NUMERIC:
+            default:
+                // "right" is the default
+                break;
+            }
+        }
+        return "";
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.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.examples.html;
+
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.util.Formatter;
+import java.util.Hashtable;
+
+/**
+ * Implementation of {@link HtmlHelper} for XSSF files.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class XSSFHtmlHelper implements HtmlHelper {
+    private final XSSFWorkbook wb;
+
+    private static final Hashtable colors = HSSFColor.getIndexHash();
+
+    public XSSFHtmlHelper(XSSFWorkbook wb) {
+        this.wb = wb;
+    }
+
+    public void colorStyles(CellStyle style, Formatter out) {
+        XSSFCellStyle cs = (XSSFCellStyle) style;
+        styleColor(out, "background-color", cs.getFillForegroundXSSFColor());
+        styleColor(out, "text-color", cs.getFont().getXSSFColor());
+    }
+
+    private void styleColor(Formatter out, String attr, XSSFColor color) {
+        if (color == null || color.isAuto())
+            return;
+
+        byte[] rgb = color.getRgb();
+        if (rgb == null) {
+            return;
+        }
+
+        // This is done twice -- rgba is new with CSS 3, and browser that don't
+        // support it will ignore the rgba specification and stick with the
+        // solid color, which is declared first
+        out.format("  %s: #%02x%02x%02x;%n", attr, rgb[0], rgb[1], rgb[2]);
+        out.format("  %s: rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x);%n", attr,
+                rgb[0], rgb[1], rgb[2], rgb[3]);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css Mon May 10 16:11:50 2010
@@ -0,0 +1,54 @@
+/*
+ * This is the default style sheet for html generated by ToHtml
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+.excelDefaults {
+	background-color: white;
+	color: black;
+	text-decoration: none;
+	direction: ltr;
+	text-transform: none;
+	text-indent: 0;
+	letter-spacing: 0;
+	word-spacing: 0;
+	white-space: normal;
+	unicode-bidi: normal;
+	vertical-align: 0;
+	background-image: none;
+	text-shadow: none;
+	list-style-image: none;
+	list-style-type: none;
+	padding: 0;
+	margin: 0;
+	border-collapse: collapse;
+	white-space: pre;
+	vertical-align: bottom;
+	font-style: normal;
+	font-family: sans-serif;
+	font-variant: normal;
+	font-weight: normal;
+	font-size: 10pt;
+	text-align: right;
+}
+
+.excelDefaults td {
+	padding: 1px 5px;
+	border: 1px solid silver;
+}
+
+.excelDefaults .colHeader {
+	background-color: silver;
+	font-weight: bold;
+	border: 1px solid black;
+	text-align: center;
+	padding: 1px 5px;
+}
+
+.excelDefaults .rowHeader {
+	background-color: silver;
+	font-weight: bold;
+	border: 1px solid black;
+	text-align: right;
+	padding: 1px 5px;
+}

Added: poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html
URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html (added)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html Mon May 10 16:11:50 2010
@@ -0,0 +1,2 @@
+This package contains an example that uses POI to convert a workbook into
+an HTML representation of the data.  It can use both XSSF and HSSF workbooks.
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellDateFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellDateFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellDateFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellDateFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,213 @@
+/* ====================================================================
+   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.text.AttributedCharacterIterator;
+import java.text.CharacterIterator;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.regex.Matcher;
+
+/**
+ * Formats a date value.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellDateFormatter extends CellFormatter {
+    private boolean amPmUpper;
+    private boolean showM;
+    private boolean showAmPm;
+    private final DateFormat dateFmt;
+    private String sFmt;
+
+    private static final long EXCEL_EPOCH_TIME;
+    private static final Date EXCEL_EPOCH_DATE;
+
+    private static final CellFormatter SIMPLE_DATE = new CellDateFormatter(
+            "mm/d/y");
+
+    static {
+        Calendar c = Calendar.getInstance();
+        c.set(1904, 0, 1, 0, 0, 0);
+        EXCEL_EPOCH_DATE = c.getTime();
+        EXCEL_EPOCH_TIME = c.getTimeInMillis();
+    }
+
+    private class DatePartHandler implements CellFormatPart.PartHandler {
+        private int mStart = -1;
+        private int mLen;
+        private int hStart = -1;
+        private int hLen;
+
+        public String handlePart(Matcher m, String part, CellFormatType type,
+                StringBuffer desc) {
+
+            int pos = desc.length();
+            char firstCh = part.charAt(0);
+            switch (firstCh) {
+            case 's':
+            case 'S':
+                if (mStart >= 0) {
+                    for (int i = 0; i < mLen; i++)
+                        desc.setCharAt(mStart + i, 'm');
+                    mStart = -1;
+                }
+                return part.toLowerCase();
+
+            case 'h':
+            case 'H':
+                mStart = -1;
+                hStart = pos;
+                hLen = part.length();
+                return part.toLowerCase();
+
+            case 'd':
+            case 'D':
+                mStart = -1;
+                if (part.length() <= 2)
+                    return part.toLowerCase();
+                else
+                    return part.toLowerCase().replace('d', 'E');
+
+            case 'm':
+            case 'M':
+                mStart = pos;
+                mLen = part.length();
+                return part.toUpperCase();
+
+            case 'y':
+            case 'Y':
+                mStart = -1;
+                if (part.length() == 3)
+                    part = "yyyy";
+                return part.toLowerCase();
+
+            case '0':
+                mStart = -1;
+                int sLen = part.length();
+                sFmt = "%0" + (sLen + 2) + "." + sLen + "f";
+                return part.replace('0', 'S');
+
+            case 'a':
+            case 'A':
+            case 'p':
+            case 'P':
+                if (part.length() > 1) {
+                    // am/pm marker
+                    mStart = -1;
+                    showAmPm = true;
+                    showM = Character.toLowerCase(part.charAt(1)) == 'm';
+                    // For some reason "am/pm" becomes AM or PM, but "a/p" becomes a or p
+                    amPmUpper = showM || Character.isUpperCase(part.charAt(0));
+
+                    return "a";
+                }
+                //noinspection fallthrough
+
+            default:
+                return null;
+            }
+        }
+
+        public void finish(StringBuffer toAppendTo) {
+            if (hStart >= 0 && !showAmPm) {
+                for (int i = 0; i < hLen; i++) {
+                    toAppendTo.setCharAt(hStart + i, 'H');
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a new date formatter with the given specification.
+     *
+     * @param format The format.
+     */
+    public CellDateFormatter(String format) {
+        super(format);
+        DatePartHandler partHandler = new DatePartHandler();
+        StringBuffer descBuf = CellFormatPart.parseFormat(format,
+                CellFormatType.DATE, partHandler);
+        partHandler.finish(descBuf);
+        dateFmt = new SimpleDateFormat(descBuf.toString());
+    }
+
+    /** {@inheritDoc} */
+    public void formatValue(StringBuffer toAppendTo, Object value) {
+        if (value == null)
+            value = 0.0;
+        if (value instanceof Number) {
+            Number num = (Number) value;
+            double v = num.doubleValue();
+            if (v == 0.0)
+                value = EXCEL_EPOCH_DATE;
+            else
+                value = new Date((long) (EXCEL_EPOCH_TIME + v));
+        }
+
+        AttributedCharacterIterator it = dateFmt.formatToCharacterIterator(
+                value);
+        boolean doneAm = false;
+        boolean doneMillis = false;
+
+        it.first();
+        for (char ch = it.first();
+             ch != CharacterIterator.DONE;
+             ch = it.next()) {
+            if (it.getAttribute(DateFormat.Field.MILLISECOND) != null) {
+                if (!doneMillis) {
+                    Date dateObj = (Date) value;
+                    int pos = toAppendTo.length();
+                    Formatter formatter = new Formatter(toAppendTo);
+                    long msecs = dateObj.getTime() % 1000;
+                    formatter.format(LOCALE, sFmt, msecs / 1000.0);
+                    toAppendTo.delete(pos, pos + 2);
+                    doneMillis = true;
+                }
+            } else if (it.getAttribute(DateFormat.Field.AM_PM) != null) {
+                if (!doneAm) {
+                    if (showAmPm) {
+                        if (amPmUpper) {
+                            toAppendTo.append(Character.toUpperCase(ch));
+                            if (showM)
+                                toAppendTo.append('M');
+                        } else {
+                            toAppendTo.append(Character.toLowerCase(ch));
+                            if (showM)
+                                toAppendTo.append('m');
+                        }
+                    }
+                    doneAm = true;
+                }
+            } else {
+                toAppendTo.append(ch);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * For a date, this is <tt>"mm/d/y"</tt>.
+     */
+    public void simpleValue(StringBuffer toAppendTo, Object value) {
+        SIMPLE_DATE.formatValue(toAppendTo, value);
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java Mon May 10 16:11:50 2010
@@ -0,0 +1,215 @@
+/* ====================================================================
+   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.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class implements printing out an elapsed time format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellElapsedFormatter extends CellFormatter {
+    private final List<TimeSpec> specs;
+    private TimeSpec topmost;
+    private final String printfFmt;
+
+    private static final Pattern PERCENTS = Pattern.compile("%");
+
+    private static final double HOUR__FACTOR = 1.0 / 24.0;
+    private static final double MIN__FACTOR = HOUR__FACTOR / 60.0;
+    private static final double SEC__FACTOR = MIN__FACTOR / 60.0;
+
+    private static class TimeSpec {
+        final char type;
+        final int pos;
+        final int len;
+        final double factor;
+        double modBy;
+
+        public TimeSpec(char type, int pos, int len, double factor) {
+            this.type = type;
+            this.pos = pos;
+            this.len = len;
+            this.factor = factor;
+            modBy = 0;
+        }
+
+        public long valueFor(double elapsed) {
+            double val;
+            if (modBy == 0)
+                val = elapsed / factor;
+            else
+                val = elapsed / factor % modBy;
+            if (type == '0')
+                return Math.round(val);
+            else
+                return (long) val;
+        }
+    }
+
+    private class ElapsedPartHandler implements CellFormatPart.PartHandler {
+        // This is the one class that's directly using printf, so it can't use
+        // the default handling for quoted strings and special characters.  The
+        // only special character for this is '%', so we have to handle all the
+        // quoting in this method ourselves.
+
+        public String handlePart(Matcher m, String part, CellFormatType type,
+                StringBuffer desc) {
+
+            int pos = desc.length();
+            char firstCh = part.charAt(0);
+            switch (firstCh) {
+            case '[':
+                if (part.length() < 3)
+                    break;
+                if (topmost != null)
+                    throw new IllegalArgumentException(
+                            "Duplicate '[' times in format");
+                part = part.toLowerCase();
+                int specLen = part.length() - 2;
+                topmost = assignSpec(part.charAt(1), pos, specLen);
+                return part.substring(1, 1 + specLen);
+
+            case 'h':
+            case 'm':
+            case 's':
+            case '0':
+                part = part.toLowerCase();
+                assignSpec(part.charAt(0), pos, part.length());
+                return part;
+
+            case '\n':
+                return "%n";
+
+            case '\"':
+                part = part.substring(1, part.length() - 1);
+                break;
+
+            case '\\':
+                part = part.substring(1);
+                break;
+
+            case '*':
+                if (part.length() > 1)
+                    part = CellFormatPart.expandChar(part);
+                break;
+
+            // An escape we can let it handle because it can't have a '%'
+            case '_':
+                return null;
+            }
+            // Replace ever "%" with a "%%" so we can use printf
+            return PERCENTS.matcher(part).replaceAll("%%");
+        }
+    }
+
+    /**
+     * Creates a elapsed time formatter.
+     *
+     * @param pattern The pattern to parse.
+     */
+    public CellElapsedFormatter(String pattern) {
+        super(pattern);
+
+        specs = new ArrayList<TimeSpec>();
+
+        StringBuffer desc = CellFormatPart.parseFormat(pattern,
+                CellFormatType.ELAPSED, new ElapsedPartHandler());
+
+        ListIterator<TimeSpec> it = specs.listIterator(specs.size());
+        while (it.hasPrevious()) {
+            TimeSpec spec = it.previous();
+            desc.replace(spec.pos, spec.pos + spec.len, "%0" + spec.len + "d");
+            if (spec.type != topmost.type) {
+                spec.modBy = modFor(spec.type, spec.len);
+            }
+        }
+
+        printfFmt = desc.toString();
+    }
+
+    private TimeSpec assignSpec(char type, int pos, int len) {
+        TimeSpec spec = new TimeSpec(type, pos, len, factorFor(type, len));
+        specs.add(spec);
+        return spec;
+    }
+
+    private static double factorFor(char type, int len) {
+        switch (type) {
+        case 'h':
+            return HOUR__FACTOR;
+        case 'm':
+            return MIN__FACTOR;
+        case 's':
+            return SEC__FACTOR;
+        case '0':
+            return SEC__FACTOR / Math.pow(10, len);
+        default:
+            throw new IllegalArgumentException(
+                    "Uknown elapsed time spec: " + type);
+        }
+    }
+
+    private static double modFor(char type, int len) {
+        switch (type) {
+        case 'h':
+            return 24;
+        case 'm':
+            return 60;
+        case 's':
+            return 60;
+        case '0':
+            return Math.pow(10, len);
+        default:
+            throw new IllegalArgumentException(
+                    "Uknown elapsed time spec: " + type);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void formatValue(StringBuffer toAppendTo, Object value) {
+        double elapsed = ((Number) value).doubleValue();
+
+        if (elapsed < 0) {
+            toAppendTo.append('-');
+            elapsed = -elapsed;
+        }
+
+        Object[] parts = new Long[specs.size()];
+        for (int i = 0; i < specs.size(); i++) {
+            parts[i] = specs.get(i).valueFor(elapsed);
+        }
+
+        Formatter formatter = new Formatter(toAppendTo);
+        formatter.format(printfFmt, parts);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * For a date, this is <tt>"mm/d/y"</tt>.
+     */
+    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/CellFormat.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormat.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormat.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormat.java Mon May 10 16:11:50 2010
@@ -0,0 +1,313 @@
+/* ====================================================================
+  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 javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Format a value according to the standard Excel behavior.  This "standard" is
+ * not explicitly documented by Microsoft, so the behavior is determined by
+ * experimentation; see the tests.
+ * <p/>
+ * An Excel format has up to four parts, separated by semicolons.  Each part
+ * specifies what to do with particular kinds of values, depending on the number
+ * of parts given: <dl> <dt>One part (example: <tt>[Green]#.##</tt>) <dd>If the
+ * value is a number, display according to this one part (example: green text,
+ * with up to two decimal points). If the value is text, display it as is.
+ * <dt>Two parts (example: <tt>[Green]#.##;[Red]#.##</tt>) <dd>If the value is a
+ * positive number or zero, display according to the first part (example: green
+ * text, with up to two decimal points); if it is a negative number, display
+ * according to the second part (example: red text, with up to two decimal
+ * points). If the value is text, display it as is. <dt>Three parts (example:
+ * <tt>[Green]#.##;[Black]#.##;[Red]#.##</tt>) <dd>If the value is a positive
+ * number, display according to the first part (example: green text, with up to
+ * two decimal points); if it is zero, display according to the second part
+ * (example: black text, with up to two decimal points); if it is a negative
+ * number, display according to the third part (example: red text, with up to
+ * two decimal points). If the value is text, display it as is. <dt>Four parts
+ * (example: <tt>[Green]#.##;[Black]#.##;[Red]#.##;[@]</tt>) <dd>If the value is
+ * a positive number, display according to the first part (example: green text,
+ * with up to two decimal points); if it is zero, display according to the
+ * second part (example: black text, with up to two decimal points); if it is a
+ * negative number, display according to the third part (example: red text, with
+ * up to two decimal points). If the value is text, display according to the
+ * fourth part (example: text in the cell's usual color, with the text value
+ * surround by brackets). </dl>
+ * <p/>
+ * In addition to these, there is a general format that is used when no format
+ * is specified.  This formatting is presented by the {@link #GENERAL_FORMAT}
+ * object.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+@SuppressWarnings({"Singleton"})
+public class CellFormat {
+    private final String format;
+    private final CellFormatPart posNumFmt;
+    private final CellFormatPart zeroNumFmt;
+    private final CellFormatPart negNumFmt;
+    private final CellFormatPart textFmt;
+
+    private static final Pattern ONE_PART = Pattern.compile(
+            CellFormatPart.FORMAT_PAT.pattern() + "(;|$)",
+            Pattern.COMMENTS | Pattern.CASE_INSENSITIVE);
+
+    private static final CellFormatPart DEFAULT_TEXT_FORMAT =
+            new CellFormatPart("@");
+
+    /**
+     * Format a value as it would be were no format specified.  This is also
+     * used when the format specified is <tt>General</tt>.
+     */
+    public static final CellFormat GENERAL_FORMAT = new CellFormat("General") {
+        @Override
+        public CellFormatResult apply(Object value) {
+            String text;
+            if (value == null) {
+                text = "";
+            } else if (value instanceof Number) {
+                text = CellNumberFormatter.SIMPLE_NUMBER.format(value);
+            } else {
+                text = value.toString();
+            }
+            return new CellFormatResult(true, text, null);
+        }
+    };
+
+    /** Maps a format string to its parsed version for efficiencies sake. */
+    private static final Map<String, CellFormat> formatCache =
+            new WeakHashMap<String, CellFormat>();
+
+    /**
+     * Returns a {@link CellFormat} that applies the given format.  Two calls
+     * with the same format may or may not return the same object.
+     *
+     * @param format The format.
+     *
+     * @return A {@link CellFormat} that applies the given format.
+     */
+    public static CellFormat getInstance(String format) {
+        CellFormat fmt = formatCache.get(format);
+        if (fmt == null) {
+            if (format.equals("General"))
+                fmt = GENERAL_FORMAT;
+            else
+                fmt = new CellFormat(format);
+            formatCache.put(format, fmt);
+        }
+        return fmt;
+    }
+
+    /**
+     * Creates a new object.
+     *
+     * @param format The format.
+     */
+    private CellFormat(String format) {
+        this.format = format;
+        Matcher m = ONE_PART.matcher(format);
+        List<CellFormatPart> parts = new ArrayList<CellFormatPart>();
+
+        while (m.find()) {
+            try {
+                String valueDesc = m.group();
+
+                // Strip out the semicolon if it's there
+                if (valueDesc.endsWith(";"))
+                    valueDesc = valueDesc.substring(0, valueDesc.length() - 1);
+
+                parts.add(new CellFormatPart(valueDesc));
+            } catch (RuntimeException e) {
+                CellFormatter.logger.log(Level.WARNING,
+                        "Invalid format: " + CellFormatter.quote(m.group()), e);
+                parts.add(null);
+            }
+        }
+
+        switch (parts.size()) {
+        case 1:
+            posNumFmt = zeroNumFmt = negNumFmt = parts.get(0);
+            textFmt = DEFAULT_TEXT_FORMAT;
+            break;
+        case 2:
+            posNumFmt = zeroNumFmt = parts.get(0);
+            negNumFmt = parts.get(1);
+            textFmt = DEFAULT_TEXT_FORMAT;
+            break;
+        case 3:
+            posNumFmt = parts.get(0);
+            zeroNumFmt = parts.get(1);
+            negNumFmt = parts.get(2);
+            textFmt = DEFAULT_TEXT_FORMAT;
+            break;
+        case 4:
+        default:
+            posNumFmt = parts.get(0);
+            zeroNumFmt = parts.get(1);
+            negNumFmt = parts.get(2);
+            textFmt = parts.get(3);
+            break;
+        }
+    }
+
+    /**
+     * Returns the result of applying the format to the given value.  If the
+     * value is a number (a type of {@link Number} object), the correct number
+     * format type is chosen; otherwise it is considered a text object.
+     *
+     * @param value The value
+     *
+     * @return The result, in a {@link CellFormatResult}.
+     */
+    public CellFormatResult apply(Object value) {
+        if (value instanceof Number) {
+            Number num = (Number) value;
+            double val = num.doubleValue();
+            if (val > 0)
+                return posNumFmt.apply(value);
+            else if (val < 0)
+                return negNumFmt.apply(-val);
+            else
+                return zeroNumFmt.apply(value);
+        } else {
+            return textFmt.apply(value);
+        }
+    }
+
+    /**
+     * Fetches the appropriate value from the cell, and returns the result of
+     * applying it to the appropriate format.  For formula cells, the computed
+     * value is what is used.
+     *
+     * @param c The cell.
+     *
+     * @return The result, in a {@link CellFormatResult}.
+     */
+    public CellFormatResult apply(Cell c) {
+        switch (ultimateType(c)) {
+        case Cell.CELL_TYPE_BLANK:
+            return apply("");
+        case Cell.CELL_TYPE_BOOLEAN:
+            return apply(c.getStringCellValue());
+        case Cell.CELL_TYPE_NUMERIC:
+            return apply(c.getNumericCellValue());
+        case Cell.CELL_TYPE_STRING:
+            return apply(c.getStringCellValue());
+        default:
+            return apply("?");
+        }
+    }
+
+    /**
+     * Uses the result of applying this format to the value, setting the text
+     * and color of a label before returning the result.
+     *
+     * @param label The label to apply to.
+     * @param value The value to process.
+     *
+     * @return The result, in a {@link CellFormatResult}.
+     */
+    public CellFormatResult apply(JLabel label, Object value) {
+        CellFormatResult result = apply(value);
+        label.setText(result.text);
+        if (result.textColor != null) {
+            label.setForeground(result.textColor);
+        }
+        return result;
+    }
+
+    /**
+     * Fetches the appropriate value from the cell, and uses the result, setting
+     * the text and color of a label before returning the result.
+     *
+     * @param label The label to apply to.
+     * @param c     The cell.
+     *
+     * @return The result, in a {@link CellFormatResult}.
+     */
+    public CellFormatResult apply(JLabel label, Cell c) {
+        switch (ultimateType(c)) {
+        case Cell.CELL_TYPE_BLANK:
+            return apply(label, "");
+        case Cell.CELL_TYPE_BOOLEAN:
+            return apply(label, c.getStringCellValue());
+        case Cell.CELL_TYPE_NUMERIC:
+            return apply(label, c.getNumericCellValue());
+        case Cell.CELL_TYPE_STRING:
+            return apply(label, c.getStringCellValue());
+        default:
+            return apply(label, "?");
+        }
+    }
+
+    /**
+     * Returns the ultimate cell type, following the results of formulas.  If
+     * the cell is a {@link Cell#CELL_TYPE_FORMULA}, this returns the result of
+     * {@link Cell#getCachedFormulaResultType()}.  Otherwise this returns the
+     * result of {@link Cell#getCellType()}.
+     *
+     * @param cell The cell.
+     *
+     * @return The ultimate type of this cell.
+     */
+    public static int ultimateType(Cell cell) {
+        int type = cell.getCellType();
+        if (type == Cell.CELL_TYPE_FORMULA)
+            return cell.getCachedFormulaResultType();
+        else
+            return type;
+    }
+
+    /**
+     * Returns <tt>true</tt> if the other object is a {@link CellFormat} object
+     * with the same format.
+     *
+     * @param obj The other object.
+     *
+     * @return <tt>true</tt> if the two objects are equal.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj instanceof CellFormat) {
+            CellFormat that = (CellFormat) obj;
+            return format.equals(that.format);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code for the format.
+     *
+     * @return A hash code for the format.
+     */
+    @Override
+    public int hashCode() {
+        return format.hashCode();
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellFormatCondition.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormatCondition.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormatCondition.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormatCondition.java Mon May 10 16:11:50 2010
@@ -0,0 +1,121 @@
+/* ====================================================================
+   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.HashMap;
+import java.util.Map;
+
+/**
+ * This object represents a condition in a cell format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public abstract class CellFormatCondition {
+    private static final int LT = 0;
+    private static final int LE = 1;
+    private static final int GT = 2;
+    private static final int GE = 3;
+    private static final int EQ = 4;
+    private static final int NE = 5;
+
+    private static final Map<String, Integer> TESTS;
+
+    static {
+        TESTS = new HashMap<String, Integer>();
+        TESTS.put("<", LT);
+        TESTS.put("<=", LE);
+        TESTS.put(">", GT);
+        TESTS.put(">=", GE);
+        TESTS.put("=", EQ);
+        TESTS.put("==", EQ);
+        TESTS.put("!=", NE);
+        TESTS.put("<>", NE);
+    }
+
+    /**
+     * Returns an instance of a condition object.
+     *
+     * @param opString The operator as a string.  One of <tt>"&lt;"</tt>,
+     *                 <tt>"&lt;="</tt>, <tt>">"</tt>, <tt>">="</tt>,
+     *                 <tt>"="</tt>, <tt>"=="</tt>, <tt>"!="</tt>, or
+     *                 <tt>"&lt;>"</tt>.
+     * @param constStr The constant (such as <tt>"12"</tt>).
+     *
+     * @return A condition object for the given condition.
+     */
+    public static CellFormatCondition getInstance(String opString,
+            String constStr) {
+
+        if (!TESTS.containsKey(opString))
+            throw new IllegalArgumentException("Unknown test: " + opString);
+        int test = TESTS.get(opString);
+
+        final double c = Double.parseDouble(constStr);
+
+        switch (test) {
+        case LT:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value < c;
+                }
+            };
+        case LE:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value <= c;
+                }
+            };
+        case GT:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value > c;
+                }
+            };
+        case GE:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value >= c;
+                }
+            };
+        case EQ:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value == c;
+                }
+            };
+        case NE:
+            return new CellFormatCondition() {
+                public boolean pass(double value) {
+                    return value != c;
+                }
+            };
+        default:
+            throw new IllegalArgumentException(
+                    "Cannot create for test number " + test + "(\"" + opString +
+                            "\")");
+        }
+    }
+
+    /**
+     * Returns <tt>true</tt> if the given value passes the constraint's test.
+     *
+     * @param value The value to compare against.
+     *
+     * @return <tt>true</tt> if the given value passes the constraint's test.
+     */
+    public abstract boolean pass(double value);
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/ss/format/CellFormatPart.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/format/CellFormatPart.java?rev=942809&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/format/CellFormatPart.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/format/CellFormatPart.java Mon May 10 16:11:50 2010
@@ -0,0 +1,494 @@
+/* ====================================================================
+   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.hssf.util.HSSFColor;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.poi.ss.format.CellFormatter.logger;
+import static org.apache.poi.ss.format.CellFormatter.quote;
+
+/**
+ * Objects of this class represent a single part of a cell format expression.
+ * Each cell can have up to four of these for positive, zero, negative, and text
+ * values.
+ * <p/>
+ * Each format part can contain a color, a condition, and will always contain a
+ * format specification.  For example <tt>"[Red][>=10]#"</tt> has a color
+ * (<tt>[Red]</tt>), a condition (<tt>>=10</tt>) and a format specification
+ * (<tt>#</tt>).
+ * <p/>
+ * This class also contains patterns for matching the subparts of format
+ * specification.  These are used internally, but are made public in case other
+ * code has use for them.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class CellFormatPart {
+    private final Color color;
+    private CellFormatCondition condition;
+    private final CellFormatter format;
+
+    private static final Map<String, Color> NAMED_COLORS;
+
+    static {
+        NAMED_COLORS = new TreeMap<String, Color>(
+                String.CASE_INSENSITIVE_ORDER);
+
+        Map colors = HSSFColor.getIndexHash();
+        for (Object val : colors.values()) {
+            HSSFColor color = (HSSFColor) val;
+            Class type = color.getClass();
+            String name = type.getSimpleName();
+            if (name.equals(name.toUpperCase())) {
+                short[] rgb = color.getTriplet();
+                Color c = new Color(rgb[0], rgb[1], rgb[2]);
+                NAMED_COLORS.put(name, c);
+                if (name.indexOf('_') > 0)
+                    NAMED_COLORS.put(name.replace('_', ' '), c);
+                if (name.indexOf("_PERCENT") > 0)
+                    NAMED_COLORS.put(name.replace("_PERCENT", "%").replace('_',
+                            ' '), c);
+            }
+        }
+    }
+
+    /** Pattern for the color part of a cell format part. */
+    public static final Pattern COLOR_PAT;
+    /** Pattern for the condition part of a cell format part. */
+    public static final Pattern CONDITION_PAT;
+    /** Pattern for the format specification part of a cell format part. */
+    public static final Pattern SPECIFICATION_PAT;
+    /** Pattern for an entire cell single part. */
+    public static final Pattern FORMAT_PAT;
+
+    /** Within {@link #FORMAT_PAT}, the group number for the matched color. */
+    public static final int COLOR_GROUP;
+    /**
+     * Within {@link #FORMAT_PAT}, the group number for the operator in the
+     * condition.
+     */
+    public static final int CONDITION_OPERATOR_GROUP;
+    /**
+     * Within {@link #FORMAT_PAT}, the group number for the value in the
+     * condition.
+     */
+    public static final int CONDITION_VALUE_GROUP;
+    /**
+     * Within {@link #FORMAT_PAT}, the group number for the format
+     * specification.
+     */
+    public static final int SPECIFICATION_GROUP;
+
+    static {
+        // A condition specification
+        String condition = "([<>=]=?|!=|<>)    # The operator\n" +
+                "  \\s*([0-9]+(?:\\.[0-9]*)?)\\s*  # The constant to test against\n";
+
+        String color =
+                "\\[(black|blue|cyan|green|magenta|red|white|yellow|color [0-9]+)\\]";
+
+        // A number specification
+        // Note: careful that in something like ##, that the trailing comma is not caught up in the integer part
+
+        // A part of a specification
+        String part = "\\\\.                 # Quoted single character\n" +
+                "|\"([^\\\\\"]|\\\\.)*\"         # Quoted string of characters (handles escaped quotes like \\\") \n" +
+                "|_.                             # Space as wide as a given character\n" +
+                "|\\*.                           # Repeating fill character\n" +
+                "|@                              # Text: cell text\n" +
+                "|([0?\\#](?:[0?\\#,]*))         # Number: digit + other digits and commas\n" +
+                "|e[-+]                          # Number: Scientific: Exponent\n" +
+                "|m{1,5}                         # Date: month or minute spec\n" +
+                "|d{1,4}                         # Date: day/date spec\n" +
+                "|y{2,4}                         # Date: year spec\n" +
+                "|h{1,2}                         # Date: hour spec\n" +
+                "|s{1,2}                         # Date: second spec\n" +
+                "|am?/pm?                        # Date: am/pm spec\n" +
+                "|\\[h{1,2}\\]                   # Elapsed time: hour spec\n" +
+                "|\\[m{1,2}\\]                   # Elapsed time: minute spec\n" +
+                "|\\[s{1,2}\\]                   # Elapsed time: second spec\n" +
+                "|[^;]                           # A character\n" + "";
+
+        String format = "(?:" + color + ")?                  # Text color\n" +
+                "(?:\\[" + condition + "\\])?                # Condition\n" +
+                "((?:" + part + ")+)                        # Format spec\n";
+
+        int flags = Pattern.COMMENTS | Pattern.CASE_INSENSITIVE;
+        COLOR_PAT = Pattern.compile(color, flags);
+        CONDITION_PAT = Pattern.compile(condition, flags);
+        SPECIFICATION_PAT = Pattern.compile(part, flags);
+        FORMAT_PAT = Pattern.compile(format, flags);
+
+        // Calculate the group numbers of important groups.  (They shift around
+        // when the pattern is changed; this way we figure out the numbers by
+        // experimentation.)
+
+        COLOR_GROUP = findGroup(FORMAT_PAT, "[Blue]@", "Blue");
+        CONDITION_OPERATOR_GROUP = findGroup(FORMAT_PAT, "[>=1]@", ">=");
+        CONDITION_VALUE_GROUP = findGroup(FORMAT_PAT, "[>=1]@", "1");
+        SPECIFICATION_GROUP = findGroup(FORMAT_PAT, "[Blue][>1]\\a ?", "\\a ?");
+    }
+
+    interface PartHandler {
+        String handlePart(Matcher m, String part, CellFormatType type,
+                StringBuffer desc);
+    }
+
+    /**
+     * Create an object to represent a format part.
+     *
+     * @param desc The string to parse.
+     */
+    public CellFormatPart(String desc) {
+        Matcher m = FORMAT_PAT.matcher(desc);
+        if (!m.matches()) {
+            throw new IllegalArgumentException("Unrecognized format: " + quote(
+                    desc));
+        }
+        color = getColor(m);
+        condition = getCondition(m);
+        format = getFormatter(m);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this format part applies to the given value. If
+     * the value is a number and this is part has a condition, returns
+     * <tt>true</tt> only if the number passes the condition.  Otherwise, this
+     * allways return <tt>true</tt>.
+     *
+     * @param valueObject The value to evaluate.
+     *
+     * @return <tt>true</tt> if this format part applies to the given value.
+     */
+    public boolean applies(Object valueObject) {
+        if (condition == null || !(valueObject instanceof Number)) {
+            if (valueObject == null)
+                throw new NullPointerException("valueObject");
+            return true;
+        } else {
+            Number num = (Number) valueObject;
+            return condition.pass(num.doubleValue());
+        }
+    }
+
+    /**
+     * Returns the number of the first group that is the same as the marker
+     * string.  The search starts with group 1.
+     *
+     * @param pat    The pattern to use.
+     * @param str    The string to match against the pattern.
+     * @param marker The marker value to find the group of.
+     *
+     * @return The matching group number.
+     *
+     * @throws IllegalArgumentException No group matches the marker.
+     */
+    private static int findGroup(Pattern pat, String str, String marker) {
+        Matcher m = pat.matcher(str);
+        if (!m.find())
+            throw new IllegalArgumentException(
+                    "Pattern \"" + pat.pattern() + "\" doesn't match \"" + str +
+                            "\"");
+        for (int i = 1; i <= m.groupCount(); i++) {
+            String grp = m.group(i);
+            if (grp != null && grp.equals(marker))
+                return i;
+        }
+        throw new IllegalArgumentException(
+                "\"" + marker + "\" not found in \"" + pat.pattern() + "\"");
+    }
+
+    /**
+     * Returns the color specification from the matcher, or <tt>null</tt> if
+     * there is none.
+     *
+     * @param m The matcher for the format part.
+     *
+     * @return The color specification or <tt>null</tt>.
+     */
+    private static Color getColor(Matcher m) {
+        String cdesc = m.group(COLOR_GROUP);
+        if (cdesc == null || cdesc.length() == 0)
+            return null;
+        Color c = NAMED_COLORS.get(cdesc);
+        if (c == null)
+            logger.warning("Unknown color: " + quote(cdesc));
+        return c;
+    }
+
+    /**
+     * Returns the condition specification from the matcher, or <tt>null</tt> if
+     * there is none.
+     *
+     * @param m The matcher for the format part.
+     *
+     * @return The condition specification or <tt>null</tt>.
+     */
+    private CellFormatCondition getCondition(Matcher m) {
+        String mdesc = m.group(CONDITION_OPERATOR_GROUP);
+        if (mdesc == null || mdesc.length() == 0)
+            return null;
+        return CellFormatCondition.getInstance(m.group(
+                CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP));
+    }
+
+    /**
+     * Returns the formatter object implied by the format specification for the
+     * format part.
+     *
+     * @param matcher The matcher for the format part.
+     *
+     * @return The formatter.
+     */
+    private CellFormatter getFormatter(Matcher matcher) {
+        String fdesc = matcher.group(SPECIFICATION_GROUP);
+        CellFormatType type = formatType(fdesc);
+        return type.formatter(fdesc);
+    }
+
+    /**
+     * Returns the type of format.
+     *
+     * @param fdesc The format specification
+     *
+     * @return The type of format.
+     */
+    private CellFormatType formatType(String fdesc) {
+        fdesc = fdesc.trim();
+        if (fdesc.equals("") || fdesc.equalsIgnoreCase("General"))
+            return CellFormatType.GENERAL;
+
+        Matcher m = SPECIFICATION_PAT.matcher(fdesc);
+        boolean couldBeDate = false;
+        boolean seenZero = false;
+        while (m.find()) {
+            String repl = m.group(0);
+            if (repl.length() > 0) {
+                switch (repl.charAt(0)) {
+                case '@':
+                    return CellFormatType.TEXT;
+                case 'd':
+                case 'D':
+                case 'y':
+                case 'Y':
+                    return CellFormatType.DATE;
+                case 'h':
+                case 'H':
+                case 'm':
+                case 'M':
+                case 's':
+                case 'S':
+                    // These can be part of date, or elapsed
+                    couldBeDate = true;
+                    break;
+                case '0':
+                    // This can be part of date, elapsed, or number
+                    seenZero = true;
+                    break;
+                case '[':
+                    return CellFormatType.ELAPSED;
+                case '#':
+                case '?':
+                    return CellFormatType.NUMBER;
+                }
+            }
+        }
+
+        // Nothing definitive was found, so we figure out it deductively
+        if (couldBeDate)
+            return CellFormatType.DATE;
+        if (seenZero)
+            return CellFormatType.NUMBER;
+        return CellFormatType.TEXT;
+    }
+
+    /**
+     * Returns a version of the original string that has any special characters
+     * quoted (or escaped) as appropriate for the cell format type.  The format
+     * type object is queried to see what is special.
+     *
+     * @param repl The original string.
+     * @param type The format type representation object.
+     *
+     * @return A version of the string with any special characters replaced.
+     *
+     * @see CellFormatType#isSpecial(char)
+     */
+    static String quoteSpecial(String repl, CellFormatType type) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < repl.length(); i++) {
+            char ch = repl.charAt(i);
+            if (ch == '\'' && type.isSpecial('\'')) {
+                sb.append('\u0000');
+                continue;
+            }
+
+            boolean special = type.isSpecial(ch);
+            if (special)
+                sb.append("'");
+            sb.append(ch);
+            if (special)
+                sb.append("'");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Apply this format part to the given value.  This returns a {@link
+     * CellFormatResult} object with the results.
+     *
+     * @param value The value to apply this format part to.
+     *
+     * @return A {@link CellFormatResult} object containing the results of
+     *         applying the format to the value.
+     */
+    public CellFormatResult apply(Object value) {
+        boolean applies = applies(value);
+        String text;
+        Color textColor;
+        if (applies) {
+            text = format.format(value);
+            textColor = color;
+        } else {
+            text = format.simpleFormat(value);
+            textColor = null;
+        }
+        return new CellFormatResult(applies, text, textColor);
+    }
+
+    /**
+     * Apply this format part to the given value, applying the result to the
+     * given label.
+     *
+     * @param label The label
+     * @param value The value to apply this format part to.
+     *
+     * @return <tt>true</tt> if the
+     */
+    public CellFormatResult apply(JLabel label, Object value) {
+        CellFormatResult result = apply(value);
+        label.setText(result.text);
+        if (result.textColor != null) {
+            label.setForeground(result.textColor);
+        }
+        return result;
+    }
+
+    public static StringBuffer parseFormat(String fdesc, CellFormatType type,
+            PartHandler partHandler) {
+
+        // Quoting is very awkward.  In the Java classes, quoting is done
+        // between ' chars, with '' meaning a single ' char. The problem is that
+        // in Excel, it is legal to have two adjacent escaped strings.  For
+        // example, consider the Excel format "\a\b#".  The naive (and easy)
+        // translation into Java DecimalFormat is "'a''b'#".  For the number 17,
+        // in Excel you would get "ab17", but in Java it would be "a'b17" -- the
+        // '' is in the middle of the quoted string in Java.  So the trick we
+        // use is this: When we encounter a ' char in the Excel format, we
+        // output a \u0000 char into the string.  Now we know that any '' in the
+        // output is the result of two adjacent escaped strings.  So after the
+        // main loop, we have to do two passes: One to eliminate any ''
+        // sequences, to make "'a''b'" become "'ab'", and another to replace any
+        // \u0000 with '' to mean a quote char.  Oy.
+        //
+        // For formats that don't use "'" we don't do any of this
+        Matcher m = SPECIFICATION_PAT.matcher(fdesc);
+        StringBuffer fmt = new StringBuffer();
+        while (m.find()) {
+            String part = group(m, 0);
+            if (part.length() > 0) {
+                String repl = partHandler.handlePart(m, part, type, fmt);
+                if (repl == null) {
+                    switch (part.charAt(0)) {
+                    case '\"':
+                        repl = quoteSpecial(part.substring(1,
+                                part.length() - 1), type);
+                        break;
+                    case '\\':
+                        repl = quoteSpecial(part.substring(1), type);
+                        break;
+                    case '_':
+                        repl = " ";
+                        break;
+                    case '*': //!! We don't do this for real, we just put in 3 of them
+                        repl = expandChar(part);
+                        break;
+                    default:
+                        repl = part;
+                        break;
+                    }
+                }
+                m.appendReplacement(fmt, Matcher.quoteReplacement(repl));
+            }
+        }
+        m.appendTail(fmt);
+
+        if (type.isSpecial('\'')) {
+            // Now the next pass for quoted characters: Remove '' chars, making "'a''b'" into "'ab'"
+            int pos = 0;
+            while ((pos = fmt.indexOf("''", pos)) >= 0) {
+                fmt.delete(pos, pos + 2);
+            }
+
+            // Now the final pass for quoted chars: Replace any \u0000 with ''
+            pos = 0;
+            while ((pos = fmt.indexOf("\u0000", pos)) >= 0) {
+                fmt.replace(pos, pos + 1, "''");
+            }
+        }
+
+        return fmt;
+    }
+
+    /**
+     * Expands a character. This is only partly done, because we don't have the
+     * correct info.  In Excel, this would be expanded to fill the rest of the
+     * cell, but we don't know, in general, what the "rest of the cell" is.
+     *
+     * @param part The character to be repeated is the second character in this
+     *             string.
+     *
+     * @return The character repeated three times.
+     */
+    static String expandChar(String part) {
+        String repl;
+        char ch = part.charAt(1);
+        repl = "" + ch + ch + ch;
+        return repl;
+    }
+
+    /**
+     * Returns the string from the group, or <tt>""</tt> if the group is
+     * <tt>null</tt>.
+     *
+     * @param m The matcher.
+     * @param g The group number.
+     *
+     * @return The group or <tt>""</tt>.
+     */
+    public static String group(Matcher m, int g) {
+        String str = m.group(g);
+        return (str == null ? "" : str);
+    }
+}
\ No newline at end of file



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