]> source.dussan.org Git - poi.git/commitdiff
Enhanced SViewer to support most border types, cell formats, and conditional formatti...
authorYegor Kozlov <yegor@apache.org>
Mon, 10 May 2010 16:11:50 +0000 (16:11 +0000)
committerYegor Kozlov <yegor@apache.org>
Mon, 10 May 2010 16:11:50 +0000 (16:11 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@942809 13f79535-47bb-0310-9956-ffa450edef68

57 files changed:
build.xml
src/contrib/src/org/apache/poi/hssf/contrib/view/SVBorder.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVFractionalFormat.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableModel.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableUtils.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java [deleted file]
src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java [deleted file]
src/documentation/content/xdocs/status.xml
src/examples/src/org/apache/poi/hssf/view/SVBorder.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVTableModel.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SViewer.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/brush/BasicBrush.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/brush/Brush.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java [new file with mode: 0644]
src/examples/src/org/apache/poi/hssf/view/brush/package.html [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css [new file with mode: 0644]
src/examples/src/org/apache/poi/ss/examples/html/package.html [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellDateFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellElapsedFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormat.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormatCondition.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormatPart.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormatResult.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormatType.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellGeneralFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellNumberFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/CellTextFormatter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/format/package.html [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/format/TestCellFormat.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java [new file with mode: 0644]
test-data/spreadsheet/DateFormatTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/ElapsedFormatTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/FormatChoiceTests.xls [new file with mode: 0644]
test-data/spreadsheet/FormatChoiceTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/FormatConditionTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/GeneralFormatTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/NumberFormatApproxTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/NumberFormatTests.xlsx [new file with mode: 0644]
test-data/spreadsheet/TextFormatTests.xlsx [new file with mode: 0644]

index 83bffa9d266f40630c9d5e07cdb840daa34c2f2f..df0dfb674fdb4dee66ad51e16eb4e58a11d476cb 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -504,6 +504,11 @@ under the License.
                 <pathelement path="${ooxml.output.dir}"/>
             </classpath>
         </javac>
+        <copy todir="${examples.output.dir}">
+          <fileset dir="${examples.src}">
+            <include name="**/*.css"/>
+          </fileset>
+        </copy>
     </target>
 
     <target name="compile-ooxml" depends="compile-main,compile-scratchpad">
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVBorder.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVBorder.java
deleted file mode 100644 (file)
index fb25448..0000000
+++ /dev/null
@@ -1,564 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.awt.*;
-
-import javax.swing.border.AbstractBorder;
-
-import org.apache.poi.hssf.usermodel.HSSFCellStyle;
-
-/**
- * This is an attempt to implement Excel style borders for the SheetViewer.
- * Mostly just overrides stuff so the javadoc won't appear here but will 
- * appear in the generated stuff.
- * 
- * @author Andrew C. Oliver (acoliver at apache dot org)
- * @author Jason Height
- */
-public class SVBorder extends AbstractBorder {
-  private Color northColor = null;
-  private Color eastColor = null;
-  private Color southColor = null;
-  private Color westColor = null;
-  private int northBorderType = HSSFCellStyle.BORDER_NONE;
-  private int eastBorderType =HSSFCellStyle.BORDER_NONE;
-  private int southBorderType = HSSFCellStyle.BORDER_NONE;
-  private int westBorderType = HSSFCellStyle.BORDER_NONE;
-  private boolean northBorder=false;
-  private boolean eastBorder=false;
-  private boolean southBorder=false;
-  private boolean westBorder=false;
-  private boolean selected = false;
-
-   public void setBorder(Color northColor, Color eastColor,
-                         Color southColor, Color westColor,
-                         int northBorderType, int eastBorderType,
-                         int southBorderType, int westBorderType,
-                         boolean selected) {
-     this.eastColor = eastColor;
-     this.southColor = southColor;
-     this.westColor = westColor;
-     this.northBorderType = northBorderType;
-     this.eastBorderType = eastBorderType;
-     this.southBorderType = southBorderType;
-     this.westBorderType = westBorderType;
-     this.northBorder=northBorderType != HSSFCellStyle.BORDER_NONE;
-     this.eastBorder=eastBorderType != HSSFCellStyle.BORDER_NONE;
-     this.southBorder=southBorderType != HSSFCellStyle.BORDER_NONE;
-     this.westBorder=westBorderType != HSSFCellStyle.BORDER_NONE;
-     this.selected = selected;
-   }
-
-   public void paintBorder(Component c, Graphics g, int x, int y, int width,
-                           int height) {
-      Color oldColor = g.getColor();
-
-
-     paintSelectedBorder(g, x, y, width, height);
-     paintNormalBorders(g, x, y, width, height);
-     paintDottedBorders(g, x, y, width, height);
-     paintDashedBorders(g, x, y, width, height);
-     paintDoubleBorders(g, x, y, width, height);
-     paintDashDotDotBorders(g, x, y, width, height);
-
-
-     g.setColor(oldColor);
-   }
-
-   /**
-    * Called by paintBorder to paint the border of a selected cell.
-    * The paramaters are the Graphics object, location and dimensions of the 
-    * cell.
-    */
-   private void paintSelectedBorder(Graphics g, int x, int y, int width,
-                                  int height) {
-     if (selected) {
-       //Need to setup thickness of 2
-       g.setColor(Color.black);
-       //paint the border
-       g.drawRect(x,y,width-1,height-1);
-
-       //paint the filled rectangle at the bottom left hand position
-       g.fillRect(x+width-5, y+height-5, 5, 5);
-     }
-   }
-
-
-   /**
-    * Called by paintBorder to paint the various versions of normal line
-    * borders for a cell.  
-    */
-   private void paintNormalBorders(Graphics g, int x, int y, int width,
-                                  int height) {
-
-      if (northBorder &&
-             ((northBorderType == HSSFCellStyle.BORDER_THIN) ||
-              (northBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
-              (northBorderType == HSSFCellStyle.BORDER_THICK)
-             )
-         ) {
-
-        int thickness = getThickness(northBorderType);
-
-       g.setColor(northColor);
-
-        for (int k=0; k < thickness; k++) {
-           g.drawLine(x,y+k,width,y+k);
-        }
-      }
-
-      if (eastBorder &&
-             ((eastBorderType == HSSFCellStyle.BORDER_THIN) ||
-              (eastBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
-              (eastBorderType == HSSFCellStyle.BORDER_THICK)
-             )
-         ) {
-
-        int thickness = getThickness(eastBorderType);
-
-       g.setColor(eastColor);
-
-        for (int k=0; k < thickness; k++) {
-           g.drawLine(width-k,y,width-k,height);
-        }
-      }
-
-      if (southBorder &&
-              ((southBorderType == HSSFCellStyle.BORDER_THIN) ||
-               (southBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
-               (southBorderType == HSSFCellStyle.BORDER_THICK)
-              )
-         ) {
-
-        int thickness = getThickness(southBorderType);
-
-       g.setColor(southColor);
-        for (int k=0; k < thickness; k++) {
-           g.drawLine(x,height - k,width,height - k);
-        }
-      }
-
-      if (westBorder &&
-             ((westBorderType == HSSFCellStyle.BORDER_THIN) ||
-              (westBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
-              (westBorderType == HSSFCellStyle.BORDER_THICK)
-             )
-         ) {
-
-        int thickness = getThickness(westBorderType);
-
-       g.setColor(westColor);
-
-        for (int k=0; k < thickness; k++) {
-           g.drawLine(x+k,y,x+k,height);
-        }
-      }
-   }
-
-   /**
-    * Called by paintBorder to paint the dotted line
-    * borders for a cell.  
-    */
-   private void paintDottedBorders(Graphics g, int x, int y, int width,
-                                  int height) {
-      if (northBorder &&
-             northBorderType == HSSFCellStyle.BORDER_DOTTED) {
-        int thickness = getThickness(northBorderType);
-
-       g.setColor(northColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int xc = x; xc < width; xc=xc+2) {
-             g.drawLine(xc,y+k,xc,y+k);
-           }
-        }
-      }
-
-      if (eastBorder &&
-              eastBorderType == HSSFCellStyle.BORDER_DOTTED
-         ) {
-
-        int thickness = getThickness(eastBorderType);
-        thickness++; //need for dotted borders to show up east
-
-       g.setColor(eastColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int yc=y;yc < height; yc=yc+2) {
-                g.drawLine(width-k,yc,width-k,yc);
-           }
-        }
-      }
-
-      if (southBorder &&
-              southBorderType == HSSFCellStyle.BORDER_DOTTED
-         ) {
-
-        int thickness = getThickness(southBorderType);
-        thickness++;
-       g.setColor(southColor);
-        for (int k=0; k < thickness; k++) {
-           for (int xc = x; xc < width; xc=xc+2) {
-             g.drawLine(xc,height-k,xc,height-k);
-           }
-        }
-      }
-
-      if (westBorder &&
-            westBorderType == HSSFCellStyle.BORDER_DOTTED
-         ) {
-
-        int thickness = getThickness(westBorderType);
-//        thickness++;
-
-       g.setColor(westColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int yc=y;yc < height; yc=yc+2) {
-                g.drawLine(x+k,yc,x+k,yc);
-           }
-        }
-      }
-   }
-
-   /**
-    * Called by paintBorder to paint the various versions of dotted line
-    * borders for a cell.  
-    */
-   private void paintDashedBorders(Graphics g, int x, int y, int width,
-                                  int height) {
-      if (northBorder &&
-             ((northBorderType == HSSFCellStyle.BORDER_DASHED) ||
-              (northBorderType == HSSFCellStyle.BORDER_HAIR))
-         ) {
-        int thickness = getThickness(northBorderType);
-
-        int dashlength = 1;
-
-        if (northBorderType == HSSFCellStyle.BORDER_DASHED)
-           dashlength = 2;
-
-       g.setColor(northColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int xc = x; xc < width; xc=xc+5) {
-             g.drawLine(xc,y+k,xc+dashlength,y+k);
-           }
-        }
-      }
-
-      if (eastBorder &&
-              ((eastBorderType == HSSFCellStyle.BORDER_DASHED) ||
-               (eastBorderType == HSSFCellStyle.BORDER_HAIR))
-         ) {
-
-        int thickness = getThickness(eastBorderType);
-        thickness++; //need for dotted borders to show up east
-
-
-        int dashlength = 1;
-
-        if (eastBorderType == HSSFCellStyle.BORDER_DASHED)
-           dashlength = 2;
-
-       g.setColor(eastColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int yc=y;yc < height; yc=yc+5) {
-                g.drawLine(width-k,yc,width-k,yc+dashlength);
-           }
-        }
-      }
-
-      if (southBorder &&
-              ((southBorderType == HSSFCellStyle.BORDER_DASHED) ||
-               (southBorderType == HSSFCellStyle.BORDER_HAIR))
-         ) {
-
-        int thickness = getThickness(southBorderType);
-        thickness++;
-
-        int dashlength = 1;
-
-        if (southBorderType == HSSFCellStyle.BORDER_DASHED)
-           dashlength = 2;
-
-       g.setColor(southColor);
-        for (int k=0; k < thickness; k++) {
-           for (int xc = x; xc < width; xc=xc+5) {
-             g.drawLine(xc,height-k,xc+dashlength,height-k);
-           }
-        }
-      }
-
-      if (westBorder &&
-            ((westBorderType == HSSFCellStyle.BORDER_DASHED) ||
-             (westBorderType == HSSFCellStyle.BORDER_HAIR))
-         ) {
-
-        int thickness = getThickness(westBorderType);
-//        thickness++;
-
-        int dashlength = 1;
-
-        if (westBorderType == HSSFCellStyle.BORDER_DASHED)
-           dashlength = 2;
-
-       g.setColor(westColor);
-
-        for (int k=0; k < thickness; k++) {
-           for (int yc=y;yc < height; yc=yc+5) {
-                g.drawLine(x+k,yc,x+k,yc+dashlength);
-           }
-        }
-      }
-   }
-
-   /**
-    * Called by paintBorder to paint the double line
-    * borders for a cell.  
-    */
-   private void paintDoubleBorders(Graphics g, int x, int y, int width,
-                                  int height) {
-      if (northBorder &&
-             northBorderType == HSSFCellStyle.BORDER_DOUBLE) {
-
-       g.setColor(northColor);
-
-        int leftx=x;
-        int rightx=width;
-
-                // if there are borders on the west or east then
-                // the second line shouldn't cross them
-        if (westBorder)
-           leftx = x+3;
-
-        if (eastBorder)
-           rightx = width-3;
-
-           g.drawLine(x,y,width,y);
-           g.drawLine(leftx,y+2,rightx,y+2);
-      }
-
-      if (eastBorder &&
-              eastBorderType == HSSFCellStyle.BORDER_DOUBLE
-         ) {
-
-        int thickness = getThickness(eastBorderType);
-        thickness++; //need for dotted borders to show up east
-
-       g.setColor(eastColor);
-
-        int topy=y;
-        int bottomy=height;
-
-        if (northBorder)
-          topy=y+3;
-
-        if (southBorder)
-            bottomy=height-3;
-
-        g.drawLine(width-1,y,width-1,height);
-        g.drawLine(width-3,topy,width-3,bottomy);
-      }
-
-      if (southBorder &&
-              southBorderType == HSSFCellStyle.BORDER_DOUBLE
-         ) {
-
-       g.setColor(southColor);
-
-        int leftx=y;
-        int rightx=width;
-
-        if (westBorder)
-           leftx=x+3;
-
-        if (eastBorder)
-           rightx=width-3;
-
-
-        g.drawLine(x,height - 1,width,height - 1);
-        g.drawLine(leftx,height - 3,rightx,height - 3);
-      }
-
-      if (westBorder &&
-            westBorderType == HSSFCellStyle.BORDER_DOUBLE
-         ) {
-
-        int thickness = getThickness(westBorderType);
-//        thickness++;
-
-       g.setColor(westColor);
-
-        int topy=y;
-        int bottomy=height-3;
-
-        if (northBorder)
-           topy=y+2;
-
-        if (southBorder)
-           bottomy=height-3;
-
-        g.drawLine(x,y,x,height);
-        g.drawLine(x+2,topy,x+2,bottomy);
-      }
-   }
-
-   /**
-    * Called by paintBorder to paint the various versions of dash dot dot line
-    * borders for a cell.  
-    */
-   private void paintDashDotDotBorders(Graphics g, int x, int y, int width,
-                                  int height) {
-      if (northBorder &&
-             ((northBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
-              (northBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
-         ) {
-        int thickness = getThickness(northBorderType);
-
-       g.setColor(northColor);
-        for (int l=x; l < width;) {
-          l=l+drawDashDotDot(g, l, y, thickness, true, true);
-        }
-
-      }
-
-      if (eastBorder &&
-              ((eastBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
-               (eastBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
-         ) {
-
-        int thickness = getThickness(eastBorderType);
-
-       g.setColor(eastColor);
-
-        for (int l=y;l < height;) {
-          //System.err.println("drawing east");
-          l=l+drawDashDotDot(g,width-1,l,thickness,false,false);
-        }
-      }
-
-      if (southBorder &&
-              ((southBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
-               (southBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
-         ) {
-
-        int thickness = getThickness(southBorderType);
-
-       g.setColor(southColor);
-
-        for (int l=x; l < width;) {
-          //System.err.println("drawing south");
-          l=l+drawDashDotDot(g, l, height-1, thickness, true, false);
-        }
-      }
-
-      if (westBorder &&
-            ((westBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
-             (westBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
-         ) {
-
-        int thickness = getThickness(westBorderType);
-
-       g.setColor(westColor);
-
-        for (int l=y;l < height;) {
-          //System.err.println("drawing west");
-          l=l+drawDashDotDot(g,x,l,thickness,false,true);
-        }
-
-      }
-   }
-
-   /**
-    *  Draws one dash dot dot horizontally or vertically with thickness drawn
-    *  incrementally to either the right or left.
-    *
-    *  @param g graphics object for drawing with
-    *  @param x the x origin of the line
-    *  @param y the y origin of the line
-    *  @param thickness the thickness of the line
-    *  @param horizontal or vertical (true for horizontal)
-    *  @param right/bottom or left/top thickness (true for right or top),
-    *         if true then the x or y origin will be incremented to provide
-    *         thickness, if false, they'll be decremented.  For vertical
-    *         borders, x is incremented or decremented, for horizontal its y.
-    *         Just set to true for north and west, and false for east and
-    *         south.
-    *  @returns length - returns the length of the line.
-    */
-   private int drawDashDotDot(Graphics g,int x, int y, int thickness,
-                              boolean horizontal,
-                              boolean rightBottom) {
-
-      for (int t=0; t < thickness; t++) {
-         if (!rightBottom) {
-            t = 0 - t; //add negative thickness so we go the other way
-                       //then we'll decrement instead of increment.
-         }
-         if (horizontal) {
-            g.drawLine(x,y+t,x+5,y+t);
-            g.drawLine(x+8,y+t,x+10,y+t);
-            g.drawLine(x+13,y+t,x+15,y+t);
-         } else {
-            g.drawLine(x+t,y,x+t,y+5);
-            g.drawLine(x+t,y+8,x+t,y+10);
-            g.drawLine(x+t,y+13,x+t,y+15);
-         }
-      }
-      return 18;
-   }
-
-   /**
-    * @returns the line thickness for a border based on border type
-    */
-   private int getThickness(int thickness) {
-       int retval=1;
-       switch (thickness) {
-           case HSSFCellStyle.BORDER_THIN:
-             retval=2;
-             break;
-           case HSSFCellStyle.BORDER_MEDIUM:
-             retval=3;
-             break;
-           case HSSFCellStyle.BORDER_THICK:
-             retval=4;
-             break;
-           case HSSFCellStyle.BORDER_DASHED:
-             retval=1;
-             break;
-           case HSSFCellStyle.BORDER_DASH_DOT_DOT:
-             retval=1;
-             break;
-           case HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT:
-             retval=3;
-             break;
-           case HSSFCellStyle.BORDER_HAIR:
-             retval=1;
-             break;
-           default:
-             retval=1;
-       }
-       return retval;
-   }
-
-
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVFractionalFormat.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVFractionalFormat.java
deleted file mode 100644 (file)
index 5512a73..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.text.*;
-
-/**
- * This class is used to format cells into their fractional format.
- *
- * I cant be 100% sure that the same fractional value will be displayed as in
- * excel but then again it is a lossy formating mode anyway
- *
- * @author     Jason Height
- * @since      15 July 2002
- */
-public class SVFractionalFormat extends Format {
-  private short ONE_DIGIT = 1;
-  private short TWO_DIGIT = 2;
-  private short THREE_DIGIT = 3;
-  private short UNITS = 4;
-  private int units = 1;
-  private short mode = -1;
-
-  /** Constructs a new FractionalFormatter
-   *
-   *  The formatStr defines how the number will be formatted
-   *  # ?/? Up to one digit
-   *  # ??/?? Up to two digits
-   *  # ???/??? Up to three digits
-   *  # ?/2 In halves
-   *  # ?/4 In quarters
-   *  # ?/8 In eighths
-   *  # ?/16 In sixteenths
-   *  # ?/10 In tenths
-   *  # ?/100 In hundredths
-   */
-  public SVFractionalFormat(String formatStr) {
-    if ("# ?/?".equals(formatStr))
-      mode = ONE_DIGIT;
-    else if ("# ??/??".equals(formatStr))
-      mode = TWO_DIGIT;
-    else if ("# ???/???".equals(formatStr))
-      mode = THREE_DIGIT;
-    else if ("# ?/2".equals(formatStr)) {
-      mode = UNITS;
-      units = 2;
-    } else if ("# ?/4".equals(formatStr)) {
-      mode = UNITS;
-      units = 4;
-    } else if ("# ?/8".equals(formatStr)) {
-      mode = UNITS;
-      units = 8;
-    } else if ("# ?/16".equals(formatStr)) {
-      mode = UNITS;
-      units = 16;
-    } else if ("# ?/10".equals(formatStr)) {
-      mode = UNITS;
-      units = 10;
-    } else if ("# ?/100".equals(formatStr)) {
-      mode = UNITS;
-      units = 100;
-    }
-  }
-
-  /**
-   *  Returns a fractional string representation of a double to a maximum denominator size
-   *
-   * This code has been translated to java from the following web page.
-   * http://www.codeproject.com/cpp/fraction.asp
-   * Originally coded in c++ By Dean Wyant  dwyant@mindspring.com
-   * The code on the web page is freely available.
-   *
-   * @param  f       Description of the Parameter
-   * @param  MaxDen  Description of the Parameter
-   * @return         Description of the Return Value
-   */
-  private String format(final double f, final int MaxDen) {
-    long Whole = (long)f;
-    int sign = 1;
-    if (f < 0) {
-      sign = -1;
-    }
-    double Precision = 0.00001;
-    double AllowedError = Precision;
-    double d = Math.abs(f);
-    d -= Whole;
-    double Frac = d;
-    double Diff = Frac;
-    long Num = 1;
-    long Den = 0;
-    long A = 0;
-    long B = 0;
-    long i = 0;
-    if (Frac > Precision) {
-      while (true) {
-        d = 1.0 / d;
-        i = (long) (d + Precision);
-        d -= i;
-        if (A > 0) {
-          Num = i * Num + B;
-        }
-        Den = (long) (Num / Frac + 0.5);
-        Diff = Math.abs((double) Num / Den - Frac);
-        if (Den > MaxDen) {
-          if (A > 0) {
-            Num = A;
-            Den = (long) (Num / Frac + 0.5);
-            Diff = Math.abs((double) Num / Den - Frac);
-          } else {
-            Den = MaxDen;
-            Num = 1;
-            Diff = Math.abs((double) Num / Den - Frac);
-            if (Diff > Frac) {
-              Num = 0;
-              Den = 1;
-              // Keeps final check below from adding 1 and keeps Den from being 0
-              Diff = Frac;
-            }
-          }
-          break;
-        }
-        if ((Diff <= AllowedError) || (d < Precision)) {
-          break;
-        }
-        Precision = AllowedError / Diff;
-        // This calcualtion of Precision does not always provide results within
-        // Allowed Error. It compensates for loss of significant digits that occurs.
-        // It helps to round the inprecise reciprocal values to i.
-        B = A;
-        A = Num;
-      }
-    }
-    if (Num == Den) {
-      Whole++;
-      Num = 0;
-      Den = 0;
-    } else if (Den == 0) {
-      Num = 0;
-    }
-    if (sign < 0) {
-      if (Whole == 0) {
-        Num = -Num;
-      } else {
-        Whole = -Whole;
-      }
-    }
-    return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(Den).toString();
-  }
-
-  /** This method formats the double in the units specified.
-   *  The usints could be any number but in this current implementation it is
-   *  halves (2), quaters (4), eigths (8) etc
-   */
-  private String formatUnit(double f, int units) {
-    long Whole = (long)f;
-    f -= Whole;
-    long Num = Math.round(f * units);
-
-    return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(units).toString();
-  }
-
-  public final String format(double val) {
-    if (mode == ONE_DIGIT) {
-      return format(val, 9);
-    } else if (mode == TWO_DIGIT) {
-      return format(val, 99);
-    } else if (mode == THREE_DIGIT) {
-      return format(val, 999);
-    } else if (mode == UNITS) {
-      return formatUnit(val , units);
-    }
-    throw new RuntimeException("Unexpected Case");
-  }
-
-  public StringBuffer format(Object obj,
-                                      StringBuffer toAppendTo,
-                                      FieldPosition pos) {
-    if (obj instanceof Number) {
-      toAppendTo.append(format(((Number)obj).doubleValue()));
-      return toAppendTo;
-    }
-    throw new IllegalArgumentException("Can only handle Numbers");
-  }
-
-  public Object parseObject(String source,
-                                   ParsePosition status) {
-    //JMH TBD
-    return null;
-  }
-
-  public Object parseObject(String source)
-                   throws ParseException {
-    //JMH TBD
-    return null;
-  }
-
-  public Object clone() {
-    //JMH TBD
-    return null;
-  }
-
-
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java
deleted file mode 100644 (file)
index fe63dfc..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.awt.*;
-import javax.swing.*;
-import javax.swing.table.*;
-
-import org.apache.poi.hssf.usermodel.*;
-
-/**
- * This class presents the row header to the table.
- *
- *
- * @author Jason Height
- */
-public class SVRowHeader extends JList {
-  /** This model simply returns an integer number up to the number of rows
-   *  that are present in the sheet.
-   *
-   */
-  private class SVRowHeaderModel extends AbstractListModel {
-    private HSSFSheet sheet;
-
-    public SVRowHeaderModel(HSSFSheet sheet) {
-      this.sheet = sheet;
-    }
-
-    public int getSize() {
-       return sheet.getLastRowNum() + 1;
-    }
-    public Object getElementAt(int index) {
-      return Integer.toString(index+1);
-    }
-  }
-
-  /** Renderes the row number*/
-  private class RowHeaderRenderer extends JLabel implements ListCellRenderer {
-    private HSSFSheet sheet;
-    private int extraHeight;
-
-    RowHeaderRenderer(HSSFSheet sheet, JTable table, int extraHeight) {
-      this.sheet = sheet;
-      this.extraHeight = extraHeight;
-      JTableHeader header = table.getTableHeader();
-      setOpaque(true);
-      setBorder(UIManager.getBorder("TableHeader.cellBorder"));
-      setHorizontalAlignment(CENTER);
-      setForeground(header.getForeground());
-      setBackground(header.getBackground());
-      setFont(header.getFont());
-    }
-
-    public Component getListCellRendererComponent( JList list,
-           Object value, int index, boolean isSelected, boolean cellHasFocus) {
-      Dimension d = getPreferredSize();
-      HSSFRow row = sheet.getRow(index);
-      int rowHeight;
-      if(row == null) {
-         rowHeight = (int)sheet.getDefaultRowHeightInPoints();
-      } else {
-         rowHeight = (int)row.getHeightInPoints();
-      }
-      d.height = rowHeight+extraHeight;
-      setPreferredSize(d);
-      setText((value == null) ? "" : value.toString());
-      return this;
-    }
-  }
-
-  public SVRowHeader(HSSFSheet sheet, JTable table, int extraHeight) {
-    ListModel lm = new SVRowHeaderModel(sheet);
-    this.setModel(lm);
-
-    setFixedCellWidth(50);
-    setCellRenderer(new RowHeaderRenderer(sheet, table, extraHeight));
-  }
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java
deleted file mode 100644 (file)
index e7a2a5d..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-
-import javax.swing.*;
-import javax.swing.table.*;
-
-import org.apache.poi.hssf.usermodel.*;
-import org.apache.poi.hssf.util.HSSFColor;
-
-/**
- * Sheet Viewer Table Cell Editor -- not commented via javadoc as it
- * nearly completely consists of overridden methods.
- *
- * @author     Jason Height
- */
-public class SVTableCellEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
-  private static final Color black = getAWTColor(new HSSFColor.BLACK());
-  private static final Color white = getAWTColor(new HSSFColor.WHITE());
-  private Hashtable colors = HSSFColor.getIndexHash();
-
-
-  private HSSFWorkbook wb;
-  private JTextField editor;
-
-  private HSSFCell editorValue;
-
-
-  public SVTableCellEditor(HSSFWorkbook wb) {
-    this.wb = wb;
-    this.editor = new JTextField();
-  }
-
-
-  /**
-   *  Gets the cellEditable attribute of the SVTableCellEditor object
-   *
-   * @return    The cellEditable value
-   */
-  public boolean isCellEditable(java.util.EventObject e) {
-    if (e instanceof MouseEvent) {
-      return ((MouseEvent) e).getClickCount() >= 2;
-    }
-    return false;
-  }
-
-
-  public boolean shouldSelectCell(EventObject anEvent) {
-    return true;
-  }
-
-
-  public boolean startCellEditing(EventObject anEvent) {
-    System.out.println("Start Cell Editing");
-    return true;
-  }
-
-
-  public boolean stopCellEditing() {
-    System.out.println("Stop Cell Editing");
-    fireEditingStopped();
-    return true;
-  }
-
-
-  public void cancelCellEditing() {
-    System.out.println("Cancel Cell Editing");
-    fireEditingCanceled();
-  }
-
-
-  public void actionPerformed(ActionEvent e) {
-    System.out.println("Action performed");
-    stopCellEditing();
-  }
-
-
-  /**
-   *  Gets the cellEditorValue attribute of the SVTableCellEditor object
-   *
-   * @return    The cellEditorValue value
-   */
-  public Object getCellEditorValue() {
-    System.out.println("GetCellEditorValue");
-    //JMH Look at when this method is called. Should it return a HSSFCell?
-    return editor.getText();
-  }
-
-
-  /**
-   *  Gets the tableCellEditorComponent attribute of the SVTableCellEditor object
-   *
-   * @return             The tableCellEditorComponent value
-   */
-  public Component getTableCellEditorComponent(JTable table, Object value,
-      boolean isSelected,
-      int row,
-      int column) {
-    System.out.println("GetTableCellEditorComponent");
-    HSSFCell cell = (HSSFCell) value;
-    if (cell != null) {
-          HSSFCellStyle style = cell.getCellStyle();
-          HSSFFont f = wb.getFontAt(style.getFontIndex());
-          boolean isbold = f.getBoldweight() > HSSFFont.BOLDWEIGHT_NORMAL;
-          boolean isitalics = f.getItalic();
-
-          int fontstyle = Font.PLAIN;
-
-          if (isbold) fontstyle = Font.BOLD;
-          if (isitalics) fontstyle = fontstyle | Font.ITALIC;
-
-          int fontheight = f.getFontHeightInPoints();
-          if (fontheight == 9) fontheight = 10; //fix for stupid ol Windows
-
-          Font font = new Font(f.getFontName(),fontstyle,fontheight);
-          editor.setFont(font);
-
-          if (style.getFillPattern() == HSSFCellStyle.SOLID_FOREGROUND) {
-            editor.setBackground(getAWTColor(style.getFillForegroundColor(), white));
-          } else editor.setBackground(white);
-
-          editor.setForeground(getAWTColor(f.getColor(), black));
-
-
-      //Set the value that is rendered for the cell
-      switch (cell.getCellType()) {
-        case HSSFCell.CELL_TYPE_BLANK:
-          editor.setText("");
-          break;
-        case HSSFCell.CELL_TYPE_BOOLEAN:
-          if (cell.getBooleanCellValue()) {
-            editor.setText("true");
-          } else {
-            editor.setText("false");
-          }
-          break;
-        case HSSFCell.CELL_TYPE_NUMERIC:
-          editor.setText(Double.toString(cell.getNumericCellValue()));
-          break;
-        case HSSFCell.CELL_TYPE_STRING:
-          editor.setText(cell.getRichStringCellValue().getString());
-          break;
-        case HSSFCell.CELL_TYPE_FORMULA:
-        default:
-          editor.setText("?");
-      }
-      switch (style.getAlignment()) {
-        case HSSFCellStyle.ALIGN_LEFT:
-        case HSSFCellStyle.ALIGN_JUSTIFY:
-        case HSSFCellStyle.ALIGN_FILL:
-          editor.setHorizontalAlignment(SwingConstants.LEFT);
-          break;
-        case HSSFCellStyle.ALIGN_CENTER:
-        case HSSFCellStyle.ALIGN_CENTER_SELECTION:
-          editor.setHorizontalAlignment(SwingConstants.CENTER);
-          break;
-        case HSSFCellStyle.ALIGN_GENERAL:
-        case HSSFCellStyle.ALIGN_RIGHT:
-          editor.setHorizontalAlignment(SwingConstants.RIGHT);
-          break;
-        default:
-          editor.setHorizontalAlignment(SwingConstants.LEFT);
-          break;
-      }
-
-    }
-    return editor;
-  }
-
-    /** This method retrieves the AWT Color representation from the colour hash table
-     *
-     */
-    private final Color getAWTColor(int index, Color deflt) {
-      HSSFColor clr = (HSSFColor)colors.get(Integer.valueOf(index));
-      if (clr == null) return deflt;
-      return getAWTColor(clr);
-    }
-
-    private static final Color getAWTColor(HSSFColor clr) {
-      short[] rgb = clr.getTriplet();
-      return new Color(rgb[0],rgb[1],rgb[2]);
-    }
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java
deleted file mode 100644 (file)
index 0e4873b..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import javax.swing.*;
-import javax.swing.table.TableCellRenderer;
-import javax.swing.border.*;
-
-import java.awt.Component;
-import java.awt.Color;
-import java.awt.Rectangle;
-
-import java.io.Serializable;
-import java.text.*;
-
-import org.apache.poi.hssf.usermodel.*;
-
-
-/**
- * Sheet Viewer Table Cell Render -- not commented via javadoc as it
- * nearly completely consists of overridden methods.
- *
- * @author Andrew C. Oliver
- */
-public class SVTableCellRenderer extends JLabel
-    implements TableCellRenderer, Serializable
-{
-    protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
-    protected SVBorder cellBorder = new SVBorder();
-
-
-    private HSSFWorkbook wb = null;
-
-    /** This class holds the references to the predefined cell formats.
-     */
-    private class CellFormatter {
-      private Format[] textFormatter;
-
-      private DecimalFormat generalNumberFormat = new DecimalFormat("0");
-
-      public CellFormatter() {
-        textFormatter = new Format[0x31];
-
-        textFormatter[0x01] = new DecimalFormat("0");
-        textFormatter[0x02] = new DecimalFormat("0.00");
-        textFormatter[0x03] = new DecimalFormat("#,##0");
-        textFormatter[0x04] = new DecimalFormat("#,##0.00");
-        textFormatter[0x05] = new DecimalFormat("$#,##0;$#,##0");
-        textFormatter[0x06] = new DecimalFormat("$#,##0;$#,##0");
-        textFormatter[0x07] = new DecimalFormat("$#,##0.00;$#,##0.00");
-        textFormatter[0x08] = new DecimalFormat("$#,##0.00;$#,##0.00");
-        textFormatter[0x09] = new DecimalFormat("0%");
-        textFormatter[0x0A] = new DecimalFormat("0.00%");
-        textFormatter[0x0B] = new DecimalFormat("0.00E0");
-        textFormatter[0x0C] = new SVFractionalFormat("# ?/?");
-        textFormatter[0x0D] = new SVFractionalFormat("# ??/??");
-        textFormatter[0x0E] = new SimpleDateFormat("M/d/yy");
-        textFormatter[0x0F] = new SimpleDateFormat("d-MMM-yy");
-        textFormatter[0x10] = new SimpleDateFormat("d-MMM");
-        textFormatter[0x11] = new SimpleDateFormat("MMM-yy");
-        textFormatter[0x12] = new SimpleDateFormat("h:mm a");
-        textFormatter[0x13] = new SimpleDateFormat("h:mm:ss a");
-        textFormatter[0x14] = new SimpleDateFormat("h:mm");
-        textFormatter[0x15] = new SimpleDateFormat("h:mm:ss");
-        textFormatter[0x16] = new SimpleDateFormat("M/d/yy h:mm");
-        // 0x17 - 0x24 reserved for international and undocumented 0x25, "(#,##0_);(#,##0)"
-        //start at 0x26
-        //jmh need to do colour
-        //"(#,##0_);[Red](#,##0)"
-        textFormatter[0x26] = new DecimalFormat("#,##0;#,##0");
-        //jmh need to do colour
-        //(#,##0.00_);(#,##0.00)
-        textFormatter[0x27] = new DecimalFormat("#,##0.00;#,##0.00");
-        textFormatter[0x28] = new DecimalFormat("#,##0.00;#,##0.00");
-//??        textFormatter[0x29] = new DecimalFormat("_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)");
-//??        textFormatter[0x2A] = new DecimalFormat("_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)");
-//??        textFormatter[0x2B] = new DecimalFormat("_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)");
-//??        textFormatter[0x2C] = new DecimalFormat("_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)");
-        textFormatter[0x2D] = new SimpleDateFormat("mm:ss");
-//??        textFormatter[0x2E] = new SimpleDateFormat("[h]:mm:ss");
-        textFormatter[0x2F] = new SimpleDateFormat("mm:ss.0");
-        textFormatter[0x30] = new DecimalFormat("##0.0E0");
-      }
-
-      public String format(short index, Object value) {
-        if (index == 0)
-          return value.toString();
-        if (textFormatter[index] == null)
-          throw new RuntimeException("Sorry. I cant handle the format code :"+Integer.toHexString(index));
-        return textFormatter[index].format(value);
-      }
-
-      public String format(short index, double value) {
-        if ( index <= 0 )
-          return generalNumberFormat.format(value);
-        if (textFormatter[index] == null)
-          throw new RuntimeException("Sorry. I cant handle the format code :"+Integer.toHexString(index));
-        if (textFormatter[index] instanceof DecimalFormat) {
-          return ((DecimalFormat)textFormatter[index]).format(value);
-        }
-        if (textFormatter[index] instanceof SVFractionalFormat) {
-          return ((SVFractionalFormat)textFormatter[index]).format(value);
-        }
-        throw new RuntimeException("Sorry. I cant handle a non decimal formatter for a decimal value :"+Integer.toHexString(index));
-      }
-
-      public boolean useRedColor(short index, double value) {
-        return (((index == 0x06)||(index == 0x08)||(index == 0x26) || (index == 0x27)) && (value < 0));
-      }
-    }
-
-    private final CellFormatter cellFormatter = new CellFormatter();
-
-    public SVTableCellRenderer(HSSFWorkbook wb) {
-       super();
-       setOpaque(true);
-        setBorder(noFocusBorder);
-        this.wb = wb;
-    }
-
-    public Component getTableCellRendererComponent(JTable table, Object value,
-                          boolean isSelected, boolean hasFocus, int row, int column) {
-       boolean isBorderSet = false;
-
-        //If the JTables default cell renderer has been setup correctly the
-        //value will be the HSSFCell that we are trying to render
-        HSSFCell c = (HSSFCell)value;
-
-        if (c != null) {
-          HSSFCellStyle s = c.getCellStyle();
-          HSSFFont f = wb.getFontAt(s.getFontIndex());
-          setFont(SVTableUtils.makeFont(f));
-
-          if (s.getFillPattern() == HSSFCellStyle.SOLID_FOREGROUND) {
-            setBackground(SVTableUtils.getAWTColor(s.getFillForegroundColor(), SVTableUtils.white));
-          } else setBackground(SVTableUtils.white);
-
-          setForeground(SVTableUtils.getAWTColor(f.getColor(), SVTableUtils.black));
-
-          cellBorder.setBorder(SVTableUtils.getAWTColor(s.getTopBorderColor(), SVTableUtils.black),
-                               SVTableUtils.getAWTColor(s.getRightBorderColor(), SVTableUtils.black),
-                               SVTableUtils.getAWTColor(s.getBottomBorderColor(), SVTableUtils.black),
-                               SVTableUtils.getAWTColor(s.getLeftBorderColor(), SVTableUtils.black),
-                               s.getBorderTop(), s.getBorderRight(),
-                               s.getBorderBottom(), s.getBorderLeft(),
-                               hasFocus);
-            setBorder(cellBorder);
-            isBorderSet=true;
-
-            //Set the value that is rendered for the cell
-            switch (c.getCellType()) {
-              case HSSFCell.CELL_TYPE_BLANK:
-                setValue("");
-              break;
-              case HSSFCell.CELL_TYPE_BOOLEAN:
-                if (c.getBooleanCellValue()) {
-                  setValue("true");
-                } else {
-                  setValue("false");
-                }
-              break;
-              case HSSFCell.CELL_TYPE_NUMERIC:
-                short format = s.getDataFormat();
-                double numericValue = c.getNumericCellValue();
-                if (cellFormatter.useRedColor(format, numericValue))
-                  setForeground(Color.red);
-                else setForeground(null);
-                setValue(cellFormatter.format(format, c.getNumericCellValue()));
-              break;
-              case HSSFCell.CELL_TYPE_STRING:
-                setValue(c.getRichStringCellValue().getString());
-              break;
-              case HSSFCell.CELL_TYPE_FORMULA:
-              default:
-                setValue("?");
-            }
-            //Set the text alignment of the cell
-            switch (s.getAlignment()) {
-              case HSSFCellStyle.ALIGN_LEFT:
-              case HSSFCellStyle.ALIGN_JUSTIFY:
-              case HSSFCellStyle.ALIGN_FILL:
-                setHorizontalAlignment(SwingConstants.LEFT);
-                break;
-              case HSSFCellStyle.ALIGN_CENTER:
-              case HSSFCellStyle.ALIGN_CENTER_SELECTION:
-                setHorizontalAlignment(SwingConstants.CENTER);
-                break;
-              case HSSFCellStyle.ALIGN_GENERAL:
-              case HSSFCellStyle.ALIGN_RIGHT:
-                setHorizontalAlignment(SwingConstants.RIGHT);
-                break;
-              default:
-                setHorizontalAlignment(SwingConstants.LEFT);
-                break;
-            }
-        } else {
-          setValue("");
-          setBackground(SVTableUtils.white);
-        }
-
-
-       if (hasFocus) {
-            if (!isBorderSet) {
-              //This is the border to paint when there is no border
-              //and the cell has focus
-              cellBorder.setBorder(SVTableUtils.black,
-                                   SVTableUtils.black,
-                                   SVTableUtils.black,
-                                   SVTableUtils.black,
-                                   HSSFCellStyle.BORDER_NONE,
-                                   HSSFCellStyle.BORDER_NONE,
-                                   HSSFCellStyle.BORDER_NONE,
-                                   HSSFCellStyle.BORDER_NONE,
-                                   isSelected);
-              setBorder(cellBorder);
-            }
-           if (table.isCellEditable(row, column)) {
-               setForeground( UIManager.getColor("Table.focusCellForeground") );
-               setBackground( UIManager.getColor("Table.focusCellBackground") );
-           }
-       } else if (!isBorderSet) {
-           setBorder(noFocusBorder);
-       }
-
-       // ---- begin optimization to avoid painting background ----
-       Color back = getBackground();
-       boolean colorMatch = (back != null) && ( back.equals(table.getBackground()) ) && table.isOpaque();
-        setOpaque(!colorMatch);
-       // ---- end optimization to aviod painting background ----
-       return this;
-    }
-
-    public void validate() {}
-
-    public void revalidate() {}
-
-    public void repaint(long tm, int x, int y, int width, int height) {}
-
-    public void repaint(Rectangle r) { }
-
-    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
-       // Strings get interned...
-       if (propertyName=="text") {
-           super.firePropertyChange(propertyName, oldValue, newValue);
-       }
-    }
-
-    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
-
-    /**
-     * Sets the string to either the value or "" if the value is null.
-     *
-     */
-    protected void setValue(Object value) {
-       setText((value == null) ? "" : value.toString());
-    }
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableModel.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableModel.java
deleted file mode 100644 (file)
index c2f4bb3..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.util.Iterator;
-import javax.swing.table.*;
-
-import org.apache.poi.hssf.usermodel.HSSFRow;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
-import org.apache.poi.hssf.usermodel.HSSFCell;
-
-/**
- * Sheet Viewer Table Model - The model for the Sheet Viewer just overrides things.
- * @author Andrew C. Oliver
- */
-
-public class SVTableModel extends AbstractTableModel {
-  private HSSFSheet st = null;
-  int maxcol = 0;
-
-  public SVTableModel(HSSFSheet st, int maxcol) {
-    this.st = st;
-    this.maxcol=maxcol;
-  }
-
-  public SVTableModel(HSSFSheet st) {
-    this.st = st;
-    Iterator i = st.rowIterator();
-
-    while (i.hasNext()) {
-      HSSFRow row = (HSSFRow)i.next();
-      if (maxcol < (row.getLastCellNum()+1)) {
-         this.maxcol = row.getLastCellNum();
-      }
-    }
-  }
-
-
-  public int getColumnCount() {
-    return this.maxcol+1;
-  }
-  public Object getValueAt(int row, int col) {
-    HSSFRow r = st.getRow(row);
-    HSSFCell c = null;
-    if (r != null) {
-      c = r.getCell(col);
-    }
-    return c;
-  }
-  public int getRowCount() {
-    return st.getLastRowNum() + 1;
-  }
-
-  public Class getColumnClass(int c) {
-       return HSSFCell.class;
-  }
-
-  public boolean isCellEditable(int rowIndex, int columnIndex) {
-    return true;
-  }
-
-  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
-    if (aValue != null)
-      System.out.println("SVTableModel.setValueAt. value type = "+aValue.getClass().getName());
-    else System.out.println("SVTableModel.setValueAt. value type = null");
-  }
-
-
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableUtils.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableUtils.java
deleted file mode 100644 (file)
index 5815ea0..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.util.*;
-import java.awt.*;
-import javax.swing.border.*;
-
-import org.apache.poi.hssf.usermodel.*;
-import org.apache.poi.hssf.util.*;
-
-/**
- * SVTableCell Editor and Renderer helper functions.
- *
- * @author     Jason Height
- */
-public class SVTableUtils {
-  private final static Hashtable colors = HSSFColor.getIndexHash();
-  /**  Description of the Field */
-  public final static Color black = getAWTColor(new HSSFColor.BLACK());
-  /**  Description of the Field */
-  public final static Color white = getAWTColor(new HSSFColor.WHITE());
-  /**  Description of the Field */
-  public static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
-
-
-  /**
-   *  Creates a new font for a specific cell style
-   */
-  public static Font makeFont(HSSFFont font) {
-    boolean isbold = font.getBoldweight() > HSSFFont.BOLDWEIGHT_NORMAL;
-    boolean isitalics = font.getItalic();
-    int fontstyle = Font.PLAIN;
-    if (isbold) {
-      fontstyle = Font.BOLD;
-    }
-    if (isitalics) {
-      fontstyle = fontstyle | Font.ITALIC;
-    }
-
-    int fontheight = font.getFontHeightInPoints();
-    if (fontheight == 9) {
-      //fix for stupid ol Windows
-      fontheight = 10;
-    }
-
-    return new Font(font.getFontName(), fontstyle, fontheight);
-  }
-
-
-  /**
-   * This method retrieves the AWT Color representation from the colour hash table
-   *
-   * @param  index  Description of the Parameter
-   * @param  deflt  Description of the Parameter
-   * @return        The aWTColor value
-   */
-  public final static Color getAWTColor(int index, Color deflt) {
-    HSSFColor clr = (HSSFColor) colors.get(Integer.valueOf(index));
-    if (clr == null) {
-      return deflt;
-    }
-    return getAWTColor(clr);
-  }
-
-
-  /**
-   *  Gets the aWTColor attribute of the SVTableUtils class
-   *
-   * @param  clr  Description of the Parameter
-   * @return      The aWTColor value
-   */
-  public final static Color getAWTColor(HSSFColor clr) {
-    short[] rgb = clr.getTriplet();
-    return new Color(rgb[0], rgb[1], rgb[2]);
-  }
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java
deleted file mode 100644 (file)
index a3668f6..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-
-/* ====================================================================
-   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.contrib.view;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.net.*;
-import java.io.*;
-import javax.swing.*;
-
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-
-/**
- * Sheet Viewer - Views XLS files via HSSF.  Can be used as an applet with
- * filename="" or as a applications (pass the filename as the first parameter).
- * Or you can pass it a URL in a "url" parameter when run as an applet or just
- * that first parameter must start with http:// and it will guess its a url. I
- * only tested it as an applet though, so it probably won't work...you fix it.
- *
- * @author Andrew C. Oliver
- * @author Jason Height
- */
-public class SViewer extends JApplet {
-  private SViewerPanel panel;
-  boolean isStandalone = false;
-  String filename = null;
-
-  /**Get a parameter value*/
-  public String getParameter(String key, String def) {
-    return isStandalone ? System.getProperty(key, def) :
-      (getParameter(key) != null ? getParameter(key) : def);
-  }
-
-  /**Construct the applet*/
-  public SViewer() {
-  }
-
-  /**Initialize the applet*/
-  public void init() {
-    try {
-      jbInit();
-    }
-    catch(Exception e) {
-      e.printStackTrace();
-      System.exit(1);
-    }
-  }
-
-  /**Component initialization*/
-  private void jbInit() throws Exception {
-    InputStream i = null;
-    boolean isurl = false;
-    if (filename == null) filename = getParameter("filename");
-
-    if (filename == null || filename.substring(0,7).equals("http://")) {
-      isurl = true;
-      if (filename == null) filename = getParameter("url");
-      i = getXLSFromURL(filename);
-    }
-
-    HSSFWorkbook wb = null;
-    if (isurl) {
-      wb = constructWorkbook(i);
-    } else {
-      wb = constructWorkbook(filename);
-    }
-    panel = new SViewerPanel(wb, false);
-    getContentPane().setLayout(new BorderLayout());
-    getContentPane().add(panel, BorderLayout.CENTER);
-  }
-
-  private HSSFWorkbook constructWorkbook(String filename) throws FileNotFoundException, IOException {
-    HSSFWorkbook wb = null;
-      FileInputStream in = new FileInputStream(filename);
-      wb = new HSSFWorkbook(in);
-      in.close();
-    return wb;
-  }
-
-  private HSSFWorkbook constructWorkbook(InputStream in) throws IOException {
-    HSSFWorkbook wb = null;
-
-      wb = new HSSFWorkbook(in);
-      in.close();
-    return wb;
-  }
-
-  /**Start the applet*/
-  public void start() {
-  }
-  /**Stop the applet*/
-  public void stop() {
-  }
-  /**Destroy the applet*/
-  public void destroy() {
-  }
-  /**Get Applet information*/
-  public String getAppletInfo() {
-    return "Applet Information";
-  }
-  /**Get parameter info*/
-  public String[][] getParameterInfo() {
-    return null;
-  }
-
-  /**
-   * opens a url and returns an inputstream
-   *
-   */
-  private InputStream getXLSFromURL(String urlstring) throws MalformedURLException, IOException {
-    URL url = new URL(urlstring);
-    URLConnection uc = url.openConnection();
-    String field = uc.getHeaderField(0);
-    for (int i=0;field != null; i++) {
-      System.out.println(field);
-      field = uc.getHeaderField(i);
-  }
-    BufferedInputStream is = new BufferedInputStream(uc.getInputStream());
-    return is;
-  }
-
-
-  /**Main method*/
-  public static void main(String[] args) {
-    if(args.length < 1) {
-      throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
-    }
-
-    SViewer applet = new SViewer();
-    applet.isStandalone = true;
-    applet.filename = args[0];
-    Frame frame;
-    frame = new Frame() {
-      protected void processWindowEvent(WindowEvent e) {
-        super.processWindowEvent(e);
-        if (e.getID() == WindowEvent.WINDOW_CLOSING) {
-          System.exit(0);
-        }
-      }
-      public synchronized void setTitle(String title) {
-        super.setTitle(title);
-        enableEvents(AWTEvent.WINDOW_EVENT_MASK);
-      }
-    };
-    frame.setTitle("Applet Frame");
-    frame.add(applet, BorderLayout.CENTER);
-    applet.init();
-    applet.start();
-    frame.setSize(400,320);
-    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
-    frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
-    frame.setVisible(true);
-  }
-}
diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java
deleted file mode 100644 (file)
index f469537..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-/* ====================================================================
-   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.contrib.view;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.io.*;
-import javax.swing.*;
-import javax.swing.table.*;
-
-import org.apache.poi.hssf.usermodel.*;
-
-/**
- * This class presents the sheets to the user.
- *
- *
- * @author Andrew C. Oliver
- * @author Jason Height
- */
-public class SViewerPanel extends JPanel {
-  /** This field is the magic number to convert from a Character width to a
-   *  java pixel width.
-   *
-   * When the "normal" font size in a workbook changes, this effects all
-   * of the heights and widths. Unfortunately there is no way to retrieve this
-   * information, hence the MAGIC number.
-   *
-   * This number may only work for the normal style font size of Arial size 10.
-   *
-   */
-  private static final int magicCharFactor = 7;
-  /** Reference to the wookbook that is being displayed*/
-  /* package */ HSSFWorkbook wb;
-  /** Reference to the tabs component*/
-  /* package */ JTabbedPane sheetPane;
-  /** Reference to the cell renderer that is used to render all cells*/
-  private SVTableCellRenderer cellRenderer;
-  /** Reference to the cell editor that is used to edit all cells.
-   *  Only constructed if editing is allowed
-   */
-  private SVTableCellEditor cellEditor;
-  /** Flag indicating if editing is allowed. Otherwise the viewer is in
-   *  view only mode.
-   */
-  private boolean allowEdits;
-
-  /**Construct the representation of the workbook*/
-  public SViewerPanel(HSSFWorkbook wb, boolean allowEdits) {
-    this.wb = wb;
-    this.allowEdits = allowEdits;
-
-    initialiseGui();
-  }
-
-  private void initialiseGui() {
-    cellRenderer = new SVTableCellRenderer(this.wb);
-    if (allowEdits)
-      cellEditor = new SVTableCellEditor(this.wb);
-
-    //Initialise the Panel
-    sheetPane = new JTabbedPane(JTabbedPane.BOTTOM);
-
-    if (allowEdits)
-      sheetPane.addMouseListener(createTabListener());
-    int sheetCount = wb.getNumberOfSheets();
-    for (int i=0; i<sheetCount;i++) {
-      String sheetName = wb.getSheetName(i);
-      //Add the new sheet to the tabbed pane
-      sheetPane.addTab(sheetName, makeSheetView(wb.getSheetAt(i)));
-    }
-    setLayout(new BorderLayout());
-    add(sheetPane, BorderLayout.CENTER);
-  }
-
-  protected JComponent makeSheetView(HSSFSheet sheet) {
-    JTable sheetView = new JTable(new SVTableModel(sheet));
-    sheetView.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
-    sheetView.setDefaultRenderer(HSSFCell.class, cellRenderer);
-    if (allowEdits)
-      sheetView.setDefaultEditor(HSSFCell.class, cellEditor);
-    JTableHeader header = sheetView.getTableHeader();
-    //Dont allow column reordering
-    header.setReorderingAllowed(false);
-    //Only allow column resizing if editing is allowed
-    header.setResizingAllowed(allowEdits);
-
-    //Set the columns the correct size
-    TableColumnModel columns = sheetView.getColumnModel();
-    for (int i=0; i< columns.getColumnCount(); i++) {
-      TableColumn column = columns.getColumn(i);
-      int width = sheet.getColumnWidth(i);
-      //256 is because the width is in 256ths of a character
-      column.setPreferredWidth(width/256*magicCharFactor);
-    }
-
-    //Set the rows to the correct size
-    int rows = sheet.getPhysicalNumberOfRows();
-    Insets insets = cellRenderer.getInsets();
-    //Need to include the insets in the calculation of the row height to use.
-    int extraHeight = insets.bottom+insets.top;
-    for (int i=0; i< rows; i++) {
-      HSSFRow row = sheet.getRow(i);
-      if (row == null) {
-        sheetView.setRowHeight(i, (int)sheet.getDefaultRowHeightInPoints()+extraHeight);
-      } else {
-        sheetView.setRowHeight(i, (int)row.getHeightInPoints()+extraHeight);
-      }
-    }
-
-    //Add the row header to the sheet
-    SVRowHeader rowHeader = new SVRowHeader(sheet, sheetView, extraHeight);
-    JScrollPane scroll = new JScrollPane( sheetView );
-    scroll.setRowHeaderView(rowHeader);
-    return scroll;
-  }
-
-  public void paint(Graphics g) {
-    //JMH I am only overriding this to get a picture of the time taken to paint
-    long start = System.currentTimeMillis();
-    super.paint(g);
-    long elapsed = System.currentTimeMillis()-start;
-    System.out.println("Paint time = "+elapsed);
-  }
-
-  protected MouseListener createTabListener() {
-    return new TabListener();
-  }
-
-  /** This class defines the default MouseListener that listens to
-   *  mouse events in the tabbed pane
-   *
-   *  The default is to popup a menu when the event occurs over a tab
-   */
-  private class TabListener implements MouseListener {
-    public JPopupMenu popup;
-    public TabListener() {
-      popup = new JPopupMenu("Sheet");
-      popup.add(createInsertSheetAction());
-      popup.add(createDeleteSheetAction());
-      popup.add(createRenameSheetAction());
-    }
-
-    protected Action createInsertSheetAction() {
-      return new InsertSheetAction();
-    }
-
-    protected Action createDeleteSheetAction() {
-      return new DeleteSheetAction();
-    }
-
-    protected Action createRenameSheetAction() {
-      return new RenameSheetAction();
-    }
-
-
-    /** This method will display the popup if the mouseevent is a popup event
-     *  and the event occurred over a tab
-     */
-    protected void checkPopup(MouseEvent e) {
-      if (e.isPopupTrigger()) {
-        int tab = sheetPane.getUI().tabForCoordinate(sheetPane, e.getX(), e.getY());
-        if (tab != -1) {
-          popup.show(sheetPane, e.getX(), e.getY());
-        }
-      }
-    }
-
-    public void mouseClicked(MouseEvent e) {
-      checkPopup(e);
-    }
-
-    public void mousePressed(MouseEvent e) {
-      checkPopup(e);
-    }
-
-    public void mouseReleased(MouseEvent e) {
-      checkPopup(e);
-    }
-
-    public void mouseEntered(MouseEvent e) {}
-    public void mouseExited(MouseEvent e) {}
-  }
-
-  /** This class defines the action that is performed when the sheet is renamed*/
-  private class RenameSheetAction extends AbstractAction {
-    public RenameSheetAction() {
-      super("Rename");
-    }
-
-    public void actionPerformed(ActionEvent e) {
-      int tabIndex = sheetPane.getSelectedIndex();
-      if (tabIndex != -1) {
-        String newSheetName = JOptionPane.showInputDialog(sheetPane, "Enter a new Sheetname", "Rename Sheet", JOptionPane.QUESTION_MESSAGE);
-        if (newSheetName != null) {
-          wb.setSheetName(tabIndex, newSheetName);
-          sheetPane.setTitleAt(tabIndex, newSheetName);
-        }
-      }
-    }
-  }
-
-  /** This class defines the action that is performed when a sheet is inserted*/
-  private class InsertSheetAction extends AbstractAction {
-    public InsertSheetAction() {
-      super("Insert");
-    }
-
-    public void actionPerformed(ActionEvent e) {
-      //Create a new sheet then search for the sheet and make sure that the
-      //sheetPane shows it.
-      HSSFSheet newSheet = wb.createSheet();
-      for (int i=0; i<wb.getNumberOfSheets();i++) {
-        HSSFSheet sheet = wb.getSheetAt(i);
-        if (newSheet == sheet) {
-          sheetPane.insertTab(wb.getSheetName(i), null, makeSheetView(sheet), null, i);
-        }
-      }
-    }
-  }
-
-  /** This class defines the action that is performed when the sheet is deleted*/
-  private class DeleteSheetAction extends AbstractAction {
-    public DeleteSheetAction() {
-      super("Delete");
-    }
-
-    public void actionPerformed(ActionEvent e) {
-      int tabIndex = sheetPane.getSelectedIndex();
-      if (tabIndex != -1) {
-        if (JOptionPane.showConfirmDialog(sheetPane, "Are you sure that you want to delete the selected sheet", "Delete Sheet?", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
-          wb.removeSheetAt(tabIndex);
-          sheetPane.remove(tabIndex);
-        }
-      }
-    }
-  }
-
-  public boolean isEditable() {
-    return allowEdits;
-  }
-
-  /**Main method*/
-  public static void main(String[] args) {
-    if(args.length < 1) {
-      throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
-    }
-    try {
-      FileInputStream in = new FileInputStream(args[0]);
-      HSSFWorkbook wb = new HSSFWorkbook(in);
-      in.close();
-
-      SViewerPanel p = new SViewerPanel(wb, true);
-      JFrame frame;
-      frame = new JFrame() {
-        protected void processWindowEvent(WindowEvent e) {
-          super.processWindowEvent(e);
-          if (e.getID() == WindowEvent.WINDOW_CLOSING) {
-            System.exit(0);
-          }
-        }
-        public synchronized void setTitle(String title) {
-          super.setTitle(title);
-          enableEvents(AWTEvent.WINDOW_EVENT_MASK);
-        }
-      };
-      frame.setTitle("Viewer Frame");
-      frame.getContentPane().add(p, BorderLayout.CENTER);
-      frame.setSize(800,640);
-      Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
-      frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
-      frame.setVisible(true);
-    } catch (IOException ex) {
-      ex.printStackTrace();
-      System.exit(1);
-    }
-  }
-}
index 6d8c5e58c389190f7fbcb2555d2ad5799da6bca7..4a86cee548f866afab35955669c6badb5c673e7a 100644 (file)
@@ -34,6 +34,7 @@
 
     <changes>
         <release version="3.7-SNAPSHOT" date="2010-??-??">
+           <action dev="POI-DEVELOPERS" type="add">49066 - Worksheet/cell formatting, with view and HTML converter</action>
            <action dev="POI-DEVELOPERS" type="fix">49020 - Workaround Excel outputting invalid XML in button definitions by not closing BR tags</action>
            <action dev="POI-DEVELOPERS" type="fix">49050 - Improve performance of AbstractEscherHolderRecord when there are lots of Continue Records</action>
            <action dev="POI-DEVELOPERS" type="fix">49194 - Correct text size limit for OOXML .xlsx files</action>
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVBorder.java b/src/examples/src/org/apache/poi/hssf/view/SVBorder.java
new file mode 100644 (file)
index 0000000..083b9cc
--- /dev/null
@@ -0,0 +1,564 @@
+
+/* ====================================================================
+   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;
+
+import java.awt.*;
+
+import javax.swing.border.AbstractBorder;
+
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+
+/**
+ * This is an attempt to implement Excel style borders for the SheetViewer.
+ * Mostly just overrides stuff so the javadoc won't appear here but will 
+ * appear in the generated stuff.
+ * 
+ * @author Andrew C. Oliver (acoliver at apache dot org)
+ * @author Jason Height
+ */
+public class SVBorder extends AbstractBorder {
+  private Color northColor = null;
+  private Color eastColor = null;
+  private Color southColor = null;
+  private Color westColor = null;
+  private int northBorderType = HSSFCellStyle.BORDER_NONE;
+  private int eastBorderType =HSSFCellStyle.BORDER_NONE;
+  private int southBorderType = HSSFCellStyle.BORDER_NONE;
+  private int westBorderType = HSSFCellStyle.BORDER_NONE;
+  private boolean northBorder=false;
+  private boolean eastBorder=false;
+  private boolean southBorder=false;
+  private boolean westBorder=false;
+  private boolean selected = false;
+
+   public void setBorder(Color northColor, Color eastColor,
+                         Color southColor, Color westColor,
+                         int northBorderType, int eastBorderType,
+                         int southBorderType, int westBorderType,
+                         boolean selected) {
+     this.eastColor = eastColor;
+     this.southColor = southColor;
+     this.westColor = westColor;
+     this.northBorderType = northBorderType;
+     this.eastBorderType = eastBorderType;
+     this.southBorderType = southBorderType;
+     this.westBorderType = westBorderType;
+     this.northBorder=northBorderType != HSSFCellStyle.BORDER_NONE;
+     this.eastBorder=eastBorderType != HSSFCellStyle.BORDER_NONE;
+     this.southBorder=southBorderType != HSSFCellStyle.BORDER_NONE;
+     this.westBorder=westBorderType != HSSFCellStyle.BORDER_NONE;
+     this.selected = selected;
+   }
+
+   public void paintBorder(Component c, Graphics g, int x, int y, int width,
+                           int height) {
+      Color oldColor = g.getColor();
+
+
+     paintSelectedBorder(g, x, y, width, height);
+     paintNormalBorders(g, x, y, width, height);
+     paintDottedBorders(g, x, y, width, height);
+     paintDashedBorders(g, x, y, width, height);
+     paintDoubleBorders(g, x, y, width, height);
+     paintDashDotDotBorders(g, x, y, width, height);
+
+
+     g.setColor(oldColor);
+   }
+
+   /**
+    * Called by paintBorder to paint the border of a selected cell.
+    * The paramaters are the Graphics object, location and dimensions of the 
+    * cell.
+    */
+   private void paintSelectedBorder(Graphics g, int x, int y, int width,
+                                  int height) {
+     if (selected) {
+       //Need to setup thickness of 2
+       g.setColor(Color.black);
+       //paint the border
+       g.drawRect(x,y,width-1,height-1);
+
+       //paint the filled rectangle at the bottom left hand position
+       g.fillRect(x+width-5, y+height-5, 5, 5);
+     }
+   }
+
+
+   /**
+    * Called by paintBorder to paint the various versions of normal line
+    * borders for a cell.  
+    */
+   private void paintNormalBorders(Graphics g, int x, int y, int width,
+                                  int height) {
+
+      if (northBorder &&
+             ((northBorderType == HSSFCellStyle.BORDER_THIN) ||
+              (northBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
+              (northBorderType == HSSFCellStyle.BORDER_THICK)
+             )
+         ) {
+
+        int thickness = getThickness(northBorderType);
+
+       g.setColor(northColor);
+
+        for (int k=0; k < thickness; k++) {
+           g.drawLine(x,y+k,width,y+k);
+        }
+      }
+
+      if (eastBorder &&
+             ((eastBorderType == HSSFCellStyle.BORDER_THIN) ||
+              (eastBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
+              (eastBorderType == HSSFCellStyle.BORDER_THICK)
+             )
+         ) {
+
+        int thickness = getThickness(eastBorderType);
+
+       g.setColor(eastColor);
+
+        for (int k=0; k < thickness; k++) {
+           g.drawLine(width-k,y,width-k,height);
+        }
+      }
+
+      if (southBorder &&
+              ((southBorderType == HSSFCellStyle.BORDER_THIN) ||
+               (southBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
+               (southBorderType == HSSFCellStyle.BORDER_THICK)
+              )
+         ) {
+
+        int thickness = getThickness(southBorderType);
+
+       g.setColor(southColor);
+        for (int k=0; k < thickness; k++) {
+           g.drawLine(x,height - k,width,height - k);
+        }
+      }
+
+      if (westBorder &&
+             ((westBorderType == HSSFCellStyle.BORDER_THIN) ||
+              (westBorderType == HSSFCellStyle.BORDER_MEDIUM) ||
+              (westBorderType == HSSFCellStyle.BORDER_THICK)
+             )
+         ) {
+
+        int thickness = getThickness(westBorderType);
+
+       g.setColor(westColor);
+
+        for (int k=0; k < thickness; k++) {
+           g.drawLine(x+k,y,x+k,height);
+        }
+      }
+   }
+
+   /**
+    * Called by paintBorder to paint the dotted line
+    * borders for a cell.  
+    */
+   private void paintDottedBorders(Graphics g, int x, int y, int width,
+                                  int height) {
+      if (northBorder &&
+             northBorderType == HSSFCellStyle.BORDER_DOTTED) {
+        int thickness = getThickness(northBorderType);
+
+       g.setColor(northColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int xc = x; xc < width; xc=xc+2) {
+             g.drawLine(xc,y+k,xc,y+k);
+           }
+        }
+      }
+
+      if (eastBorder &&
+              eastBorderType == HSSFCellStyle.BORDER_DOTTED
+         ) {
+
+        int thickness = getThickness(eastBorderType);
+        thickness++; //need for dotted borders to show up east
+
+       g.setColor(eastColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int yc=y;yc < height; yc=yc+2) {
+                g.drawLine(width-k,yc,width-k,yc);
+           }
+        }
+      }
+
+      if (southBorder &&
+              southBorderType == HSSFCellStyle.BORDER_DOTTED
+         ) {
+
+        int thickness = getThickness(southBorderType);
+        thickness++;
+       g.setColor(southColor);
+        for (int k=0; k < thickness; k++) {
+           for (int xc = x; xc < width; xc=xc+2) {
+             g.drawLine(xc,height-k,xc,height-k);
+           }
+        }
+      }
+
+      if (westBorder &&
+            westBorderType == HSSFCellStyle.BORDER_DOTTED
+         ) {
+
+        int thickness = getThickness(westBorderType);
+//        thickness++;
+
+       g.setColor(westColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int yc=y;yc < height; yc=yc+2) {
+                g.drawLine(x+k,yc,x+k,yc);
+           }
+        }
+      }
+   }
+
+   /**
+    * Called by paintBorder to paint the various versions of dotted line
+    * borders for a cell.  
+    */
+   private void paintDashedBorders(Graphics g, int x, int y, int width,
+                                  int height) {
+      if (northBorder &&
+             ((northBorderType == HSSFCellStyle.BORDER_DASHED) ||
+              (northBorderType == HSSFCellStyle.BORDER_HAIR))
+         ) {
+        int thickness = getThickness(northBorderType);
+
+        int dashlength = 1;
+
+        if (northBorderType == HSSFCellStyle.BORDER_DASHED)
+           dashlength = 2;
+
+       g.setColor(northColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int xc = x; xc < width; xc=xc+5) {
+             g.drawLine(xc,y+k,xc+dashlength,y+k);
+           }
+        }
+      }
+
+      if (eastBorder &&
+              ((eastBorderType == HSSFCellStyle.BORDER_DASHED) ||
+               (eastBorderType == HSSFCellStyle.BORDER_HAIR))
+         ) {
+
+        int thickness = getThickness(eastBorderType);
+        thickness++; //need for dotted borders to show up east
+
+
+        int dashlength = 1;
+
+        if (eastBorderType == HSSFCellStyle.BORDER_DASHED)
+           dashlength = 2;
+
+       g.setColor(eastColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int yc=y;yc < height; yc=yc+5) {
+                g.drawLine(width-k,yc,width-k,yc+dashlength);
+           }
+        }
+      }
+
+      if (southBorder &&
+              ((southBorderType == HSSFCellStyle.BORDER_DASHED) ||
+               (southBorderType == HSSFCellStyle.BORDER_HAIR))
+         ) {
+
+        int thickness = getThickness(southBorderType);
+        thickness++;
+
+        int dashlength = 1;
+
+        if (southBorderType == HSSFCellStyle.BORDER_DASHED)
+           dashlength = 2;
+
+       g.setColor(southColor);
+        for (int k=0; k < thickness; k++) {
+           for (int xc = x; xc < width; xc=xc+5) {
+             g.drawLine(xc,height-k,xc+dashlength,height-k);
+           }
+        }
+      }
+
+      if (westBorder &&
+            ((westBorderType == HSSFCellStyle.BORDER_DASHED) ||
+             (westBorderType == HSSFCellStyle.BORDER_HAIR))
+         ) {
+
+        int thickness = getThickness(westBorderType);
+//        thickness++;
+
+        int dashlength = 1;
+
+        if (westBorderType == HSSFCellStyle.BORDER_DASHED)
+           dashlength = 2;
+
+       g.setColor(westColor);
+
+        for (int k=0; k < thickness; k++) {
+           for (int yc=y;yc < height; yc=yc+5) {
+                g.drawLine(x+k,yc,x+k,yc+dashlength);
+           }
+        }
+      }
+   }
+
+   /**
+    * Called by paintBorder to paint the double line
+    * borders for a cell.  
+    */
+   private void paintDoubleBorders(Graphics g, int x, int y, int width,
+                                  int height) {
+      if (northBorder &&
+             northBorderType == HSSFCellStyle.BORDER_DOUBLE) {
+
+       g.setColor(northColor);
+
+        int leftx=x;
+        int rightx=width;
+
+                // if there are borders on the west or east then
+                // the second line shouldn't cross them
+        if (westBorder)
+           leftx = x+3;
+
+        if (eastBorder)
+           rightx = width-3;
+
+           g.drawLine(x,y,width,y);
+           g.drawLine(leftx,y+2,rightx,y+2);
+      }
+
+      if (eastBorder &&
+              eastBorderType == HSSFCellStyle.BORDER_DOUBLE
+         ) {
+
+        int thickness = getThickness(eastBorderType);
+        thickness++; //need for dotted borders to show up east
+
+       g.setColor(eastColor);
+
+        int topy=y;
+        int bottomy=height;
+
+        if (northBorder)
+          topy=y+3;
+
+        if (southBorder)
+            bottomy=height-3;
+
+        g.drawLine(width-1,y,width-1,height);
+        g.drawLine(width-3,topy,width-3,bottomy);
+      }
+
+      if (southBorder &&
+              southBorderType == HSSFCellStyle.BORDER_DOUBLE
+         ) {
+
+       g.setColor(southColor);
+
+        int leftx=y;
+        int rightx=width;
+
+        if (westBorder)
+           leftx=x+3;
+
+        if (eastBorder)
+           rightx=width-3;
+
+
+        g.drawLine(x,height - 1,width,height - 1);
+        g.drawLine(leftx,height - 3,rightx,height - 3);
+      }
+
+      if (westBorder &&
+            westBorderType == HSSFCellStyle.BORDER_DOUBLE
+         ) {
+
+        int thickness = getThickness(westBorderType);
+//        thickness++;
+
+       g.setColor(westColor);
+
+        int topy=y;
+        int bottomy=height-3;
+
+        if (northBorder)
+           topy=y+2;
+
+        if (southBorder)
+           bottomy=height-3;
+
+        g.drawLine(x,y,x,height);
+        g.drawLine(x+2,topy,x+2,bottomy);
+      }
+   }
+
+   /**
+    * Called by paintBorder to paint the various versions of dash dot dot line
+    * borders for a cell.  
+    */
+   private void paintDashDotDotBorders(Graphics g, int x, int y, int width,
+                                  int height) {
+      if (northBorder &&
+             ((northBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
+              (northBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
+         ) {
+        int thickness = getThickness(northBorderType);
+
+       g.setColor(northColor);
+        for (int l=x; l < width;) {
+          l=l+drawDashDotDot(g, l, y, thickness, true, true);
+        }
+
+      }
+
+      if (eastBorder &&
+              ((eastBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
+               (eastBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
+         ) {
+
+        int thickness = getThickness(eastBorderType);
+
+       g.setColor(eastColor);
+
+        for (int l=y;l < height;) {
+          //System.err.println("drawing east");
+          l=l+drawDashDotDot(g,width-1,l,thickness,false,false);
+        }
+      }
+
+      if (southBorder &&
+              ((southBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
+               (southBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
+         ) {
+
+        int thickness = getThickness(southBorderType);
+
+       g.setColor(southColor);
+
+        for (int l=x; l < width;) {
+          //System.err.println("drawing south");
+          l=l+drawDashDotDot(g, l, height-1, thickness, true, false);
+        }
+      }
+
+      if (westBorder &&
+            ((westBorderType == HSSFCellStyle.BORDER_DASH_DOT_DOT) ||
+             (westBorderType == HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT))
+         ) {
+
+        int thickness = getThickness(westBorderType);
+
+       g.setColor(westColor);
+
+        for (int l=y;l < height;) {
+          //System.err.println("drawing west");
+          l=l+drawDashDotDot(g,x,l,thickness,false,true);
+        }
+
+      }
+   }
+
+   /**
+    *  Draws one dash dot dot horizontally or vertically with thickness drawn
+    *  incrementally to either the right or left.
+    *
+    *  @param g graphics object for drawing with
+    *  @param x the x origin of the line
+    *  @param y the y origin of the line
+    *  @param thickness the thickness of the line
+    *  @param horizontal or vertical (true for horizontal)
+    *  @param right/bottom or left/top thickness (true for right or top),
+    *         if true then the x or y origin will be incremented to provide
+    *         thickness, if false, they'll be decremented.  For vertical
+    *         borders, x is incremented or decremented, for horizontal its y.
+    *         Just set to true for north and west, and false for east and
+    *         south.
+    *  @returns length - returns the length of the line.
+    */
+   private int drawDashDotDot(Graphics g,int x, int y, int thickness,
+                              boolean horizontal,
+                              boolean rightBottom) {
+
+      for (int t=0; t < thickness; t++) {
+         if (!rightBottom) {
+            t = 0 - t; //add negative thickness so we go the other way
+                       //then we'll decrement instead of increment.
+         }
+         if (horizontal) {
+            g.drawLine(x,y+t,x+5,y+t);
+            g.drawLine(x+8,y+t,x+10,y+t);
+            g.drawLine(x+13,y+t,x+15,y+t);
+         } else {
+            g.drawLine(x+t,y,x+t,y+5);
+            g.drawLine(x+t,y+8,x+t,y+10);
+            g.drawLine(x+t,y+13,x+t,y+15);
+         }
+      }
+      return 18;
+   }
+
+   /**
+    * @returns the line thickness for a border based on border type
+    */
+   private int getThickness(int thickness) {
+       int retval=1;
+       switch (thickness) {
+           case HSSFCellStyle.BORDER_THIN:
+             retval=2;
+             break;
+           case HSSFCellStyle.BORDER_MEDIUM:
+             retval=3;
+             break;
+           case HSSFCellStyle.BORDER_THICK:
+             retval=4;
+             break;
+           case HSSFCellStyle.BORDER_DASHED:
+             retval=1;
+             break;
+           case HSSFCellStyle.BORDER_DASH_DOT_DOT:
+             retval=1;
+             break;
+           case HSSFCellStyle.BORDER_MEDIUM_DASH_DOT_DOT:
+             retval=3;
+             break;
+           case HSSFCellStyle.BORDER_HAIR:
+             retval=1;
+             break;
+           default:
+             retval=1;
+       }
+       return retval;
+   }
+
+
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java b/src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java
new file mode 100644 (file)
index 0000000..cd6ff6e
--- /dev/null
@@ -0,0 +1,220 @@
+
+/* ====================================================================
+   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;
+
+import java.text.*;
+
+/**
+ * This class is used to format cells into their fractional format.
+ *
+ * I cant be 100% sure that the same fractional value will be displayed as in
+ * excel but then again it is a lossy formating mode anyway
+ *
+ * @author     Jason Height
+ * @since      15 July 2002
+ */
+public class SVFractionalFormat extends Format {
+  private short ONE_DIGIT = 1;
+  private short TWO_DIGIT = 2;
+  private short THREE_DIGIT = 3;
+  private short UNITS = 4;
+  private int units = 1;
+  private short mode = -1;
+
+  /** Constructs a new FractionalFormatter
+   *
+   *  The formatStr defines how the number will be formatted
+   *  # ?/? Up to one digit
+   *  # ??/?? Up to two digits
+   *  # ???/??? Up to three digits
+   *  # ?/2 In halves
+   *  # ?/4 In quarters
+   *  # ?/8 In eighths
+   *  # ?/16 In sixteenths
+   *  # ?/10 In tenths
+   *  # ?/100 In hundredths
+   */
+  public SVFractionalFormat(String formatStr) {
+    if ("# ?/?".equals(formatStr))
+      mode = ONE_DIGIT;
+    else if ("# ??/??".equals(formatStr))
+      mode = TWO_DIGIT;
+    else if ("# ???/???".equals(formatStr))
+      mode = THREE_DIGIT;
+    else if ("# ?/2".equals(formatStr)) {
+      mode = UNITS;
+      units = 2;
+    } else if ("# ?/4".equals(formatStr)) {
+      mode = UNITS;
+      units = 4;
+    } else if ("# ?/8".equals(formatStr)) {
+      mode = UNITS;
+      units = 8;
+    } else if ("# ?/16".equals(formatStr)) {
+      mode = UNITS;
+      units = 16;
+    } else if ("# ?/10".equals(formatStr)) {
+      mode = UNITS;
+      units = 10;
+    } else if ("# ?/100".equals(formatStr)) {
+      mode = UNITS;
+      units = 100;
+    }
+  }
+
+  /**
+   *  Returns a fractional string representation of a double to a maximum denominator size
+   *
+   * This code has been translated to java from the following web page.
+   * http://www.codeproject.com/cpp/fraction.asp
+   * Originally coded in c++ By Dean Wyant  dwyant@mindspring.com
+   * The code on the web page is freely available.
+   *
+   * @param  f       Description of the Parameter
+   * @param  MaxDen  Description of the Parameter
+   * @return         Description of the Return Value
+   */
+  private String format(final double f, final int MaxDen) {
+    long Whole = (long)f;
+    int sign = 1;
+    if (f < 0) {
+      sign = -1;
+    }
+    double Precision = 0.00001;
+    double AllowedError = Precision;
+    double d = Math.abs(f);
+    d -= Whole;
+    double Frac = d;
+    double Diff = Frac;
+    long Num = 1;
+    long Den = 0;
+    long A = 0;
+    long B = 0;
+    long i = 0;
+    if (Frac > Precision) {
+      while (true) {
+        d = 1.0 / d;
+        i = (long) (d + Precision);
+        d -= i;
+        if (A > 0) {
+          Num = i * Num + B;
+        }
+        Den = (long) (Num / Frac + 0.5);
+        Diff = Math.abs((double) Num / Den - Frac);
+        if (Den > MaxDen) {
+          if (A > 0) {
+            Num = A;
+            Den = (long) (Num / Frac + 0.5);
+            Diff = Math.abs((double) Num / Den - Frac);
+          } else {
+            Den = MaxDen;
+            Num = 1;
+            Diff = Math.abs((double) Num / Den - Frac);
+            if (Diff > Frac) {
+              Num = 0;
+              Den = 1;
+              // Keeps final check below from adding 1 and keeps Den from being 0
+              Diff = Frac;
+            }
+          }
+          break;
+        }
+        if ((Diff <= AllowedError) || (d < Precision)) {
+          break;
+        }
+        Precision = AllowedError / Diff;
+        // This calcualtion of Precision does not always provide results within
+        // Allowed Error. It compensates for loss of significant digits that occurs.
+        // It helps to round the inprecise reciprocal values to i.
+        B = A;
+        A = Num;
+      }
+    }
+    if (Num == Den) {
+      Whole++;
+      Num = 0;
+      Den = 0;
+    } else if (Den == 0) {
+      Num = 0;
+    }
+    if (sign < 0) {
+      if (Whole == 0) {
+        Num = -Num;
+      } else {
+        Whole = -Whole;
+      }
+    }
+    return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(Den).toString();
+  }
+
+  /** This method formats the double in the units specified.
+   *  The usints could be any number but in this current implementation it is
+   *  halves (2), quaters (4), eigths (8) etc
+   */
+  private String formatUnit(double f, int units) {
+    long Whole = (long)f;
+    f -= Whole;
+    long Num = Math.round(f * units);
+
+    return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(units).toString();
+  }
+
+  public final String format(double val) {
+    if (mode == ONE_DIGIT) {
+      return format(val, 9);
+    } else if (mode == TWO_DIGIT) {
+      return format(val, 99);
+    } else if (mode == THREE_DIGIT) {
+      return format(val, 999);
+    } else if (mode == UNITS) {
+      return formatUnit(val , units);
+    }
+    throw new RuntimeException("Unexpected Case");
+  }
+
+  public StringBuffer format(Object obj,
+                                      StringBuffer toAppendTo,
+                                      FieldPosition pos) {
+    if (obj instanceof Number) {
+      toAppendTo.append(format(((Number)obj).doubleValue()));
+      return toAppendTo;
+    }
+    throw new IllegalArgumentException("Can only handle Numbers");
+  }
+
+  public Object parseObject(String source,
+                                   ParsePosition status) {
+    //JMH TBD
+    return null;
+  }
+
+  public Object parseObject(String source)
+                   throws ParseException {
+    //JMH TBD
+    return null;
+  }
+
+  public Object clone() {
+    //JMH TBD
+    return null;
+  }
+
+
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java b/src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java
new file mode 100644 (file)
index 0000000..c6db2f7
--- /dev/null
@@ -0,0 +1,96 @@
+
+/* ====================================================================
+   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;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.*;
+
+/**
+ * This class presents the row header to the table.
+ *
+ *
+ * @author Jason Height
+ */
+public class SVRowHeader extends JList {
+  /** This model simply returns an integer number up to the number of rows
+   *  that are present in the sheet.
+   *
+   */
+  private class SVRowHeaderModel extends AbstractListModel {
+    private HSSFSheet sheet;
+
+    public SVRowHeaderModel(HSSFSheet sheet) {
+      this.sheet = sheet;
+    }
+
+    public int getSize() {
+       return sheet.getLastRowNum() + 1;
+    }
+    public Object getElementAt(int index) {
+      return Integer.toString(index+1);
+    }
+  }
+
+  /** Renderes the row number*/
+  private class RowHeaderRenderer extends JLabel implements ListCellRenderer {
+    private HSSFSheet sheet;
+    private int extraHeight;
+
+    RowHeaderRenderer(HSSFSheet sheet, JTable table, int extraHeight) {
+      this.sheet = sheet;
+      this.extraHeight = extraHeight;
+      JTableHeader header = table.getTableHeader();
+      setOpaque(true);
+      setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+      setHorizontalAlignment(CENTER);
+      setForeground(header.getForeground());
+      setBackground(header.getBackground());
+      setFont(header.getFont());
+    }
+
+    public Component getListCellRendererComponent( JList list,
+           Object value, int index, boolean isSelected, boolean cellHasFocus) {
+      Dimension d = getPreferredSize();
+      HSSFRow row = sheet.getRow(index);
+      int rowHeight;
+      if(row == null) {
+         rowHeight = (int)sheet.getDefaultRowHeightInPoints();
+      } else {
+         rowHeight = (int)row.getHeightInPoints();
+      }
+      d.height = rowHeight+extraHeight;
+      setPreferredSize(d);
+      setText((value == null) ? "" : value.toString());
+      return this;
+    }
+  }
+
+  public SVRowHeader(HSSFSheet sheet, JTable table, int extraHeight) {
+    ListModel lm = new SVRowHeaderModel(sheet);
+    this.setModel(lm);
+
+    setFixedCellWidth(50);
+    setCellRenderer(new RowHeaderRenderer(sheet, table, extraHeight));
+  }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java b/src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java
new file mode 100644 (file)
index 0000000..ed2fd8f
--- /dev/null
@@ -0,0 +1,241 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.hssf.view.brush.PendingPaintings;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.*;
+import javax.swing.text.JTextComponent;
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+
+/**
+ * This class is a table that represents the values in a single worksheet.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class SVSheetTable extends JTable {
+  private final HSSFSheet sheet;
+  private final PendingPaintings pendingPaintings;
+  private FormulaDisplayListener formulaListener;
+  private JScrollPane scroll;
+
+  private static final Color HEADER_BACKGROUND = new Color(235, 235, 235);
+
+  /**
+   * This field is the magic number to convert from a Character width to a java
+   * pixel width.
+   * <p/>
+   * When the "normal" font size in a workbook changes, this effects all of the
+   * heights and widths. Unfortunately there is no way to retrieve this
+   * information, hence the MAGIC number.
+   * <p/>
+   * This number may only work for the normal style font size of Arial size 10.
+   */
+  private static final int magicCharFactor = 7;
+
+  private class HeaderCell extends JLabel {
+    private final int row;
+
+    public HeaderCell(Object value, int row) {
+      super(value.toString(), CENTER);
+      this.row = row;
+      setBackground(HEADER_BACKGROUND);
+      setOpaque(true);
+      setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
+      setRowSelectionAllowed(false);
+    }
+
+    @Override
+    public Dimension getPreferredSize() {
+      Dimension d = super.getPreferredSize();
+      if (row >= 0) {
+        d.height = getRowHeight(row);
+      }
+      return d;
+    }
+
+    @Override
+    public Dimension getMaximumSize() {
+      Dimension d = super.getMaximumSize();
+      if (row >= 0) {
+        d.height = getRowHeight(row);
+      }
+      return d;
+    }
+
+    @Override
+    public Dimension getMinimumSize() {
+      Dimension d = super.getMinimumSize();
+      if (row >= 0) {
+        d.height = getRowHeight(row);
+      }
+      return d;
+    }
+  }
+
+  private class HeaderCellRenderer implements TableCellRenderer {
+    public Component getTableCellRendererComponent(JTable table, Object value,
+        boolean isSelected, boolean hasFocus, int row, int column) {
+
+      return new HeaderCell(value, row);
+    }
+  }
+
+  private class FormulaDisplayListener implements ListSelectionListener {
+    private final JTextComponent formulaDisplay;
+
+    public FormulaDisplayListener(JTextComponent formulaDisplay) {
+      this.formulaDisplay = formulaDisplay;
+    }
+
+    public void valueChanged(ListSelectionEvent e) {
+      int row = getSelectedRow();
+      int col = getSelectedColumn();
+      if (row < 0 || col < 0) {
+        return;
+      }
+
+      if (e.getValueIsAdjusting()) {
+        return;
+      }
+
+      HSSFCell cell = (HSSFCell) getValueAt(row, col);
+      String formula = "";
+      if (cell != null) {
+        if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
+          formula = cell.getCellFormula();
+        } else {
+          formula = cell.toString();
+        }
+        if (formula == null)
+          formula = "";
+      }
+      formulaDisplay.setText(formula);
+    }
+  }
+
+  public SVSheetTable(HSSFSheet sheet) {
+    super(new SVTableModel(sheet));
+    this.sheet = sheet;
+
+    setIntercellSpacing(new Dimension(0, 0));
+    setAutoResizeMode(AUTO_RESIZE_OFF);
+    JTableHeader header = getTableHeader();
+    header.setDefaultRenderer(new HeaderCellRenderer());
+    pendingPaintings = new PendingPaintings(this);
+
+    //Set the columns the correct size
+    TableColumnModel columns = getColumnModel();
+    for (int i = 0; i < columns.getColumnCount(); i++) {
+      TableColumn column = columns.getColumn(i);
+      int width = sheet.getColumnWidth(i);
+      //256 is because the width is in 256ths of a character
+      column.setPreferredWidth(width / 256 * magicCharFactor);
+    }
+
+    Toolkit t = getToolkit();
+    int res = t.getScreenResolution();
+    TableModel model = getModel();
+    for (int i = 0; i < model.getRowCount(); i++) {
+      Row row = sheet.getRow(i - sheet.getFirstRowNum());
+      if (row != null) {
+        short h = row.getHeight();
+        int height = Math.round(Math.max(1, h / (res / 70 * 20) + 3));
+        System.out.printf("%d: %d (%d @ %d)%n", i, height, h, res);
+        setRowHeight(i, height);
+      }
+    }
+
+    addHierarchyListener(new HierarchyListener() {
+      public void hierarchyChanged(HierarchyEvent e) {
+        if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0) {
+          Container changedParent = e.getChangedParent();
+          if (changedParent instanceof JViewport) {
+            Container grandparent = changedParent.getParent();
+            if (grandparent instanceof JScrollPane) {
+              JScrollPane jScrollPane = (JScrollPane) grandparent;
+              setupScroll(jScrollPane);
+            }
+          }
+        }
+      }
+    });
+  }
+
+  public void setupScroll(JScrollPane scroll) {
+    if (scroll == this.scroll)
+      return;
+
+    this.scroll = scroll;
+    if (scroll == null)
+      return;
+
+    SVRowHeader rowHeader = new SVRowHeader(sheet, this, 0);
+    scroll.setRowHeaderView(rowHeader);
+    scroll.setCorner(JScrollPane.UPPER_LEADING_CORNER, headerCell("?"));
+  }
+
+  public void setFormulaDisplay(JTextComponent formulaDisplay) {
+    ListSelectionModel rowSelMod = getSelectionModel();
+    ListSelectionModel colSelMod = getColumnModel().getSelectionModel();
+
+    if (formulaDisplay == null) {
+      rowSelMod.removeListSelectionListener(formulaListener);
+      colSelMod.removeListSelectionListener(formulaListener);
+      formulaListener = null;
+    }
+
+    if (formulaDisplay != null) {
+      formulaListener = new FormulaDisplayListener(formulaDisplay);
+      rowSelMod.addListSelectionListener(formulaListener);
+      colSelMod.addListSelectionListener(formulaListener);
+    }
+  }
+
+  public JTextComponent getFormulaDisplay() {
+    if (formulaListener == null)
+      return null;
+    else
+      return formulaListener.formulaDisplay;
+  }
+
+  public Component headerCell(String text) {
+    return new HeaderCell(text, -1);
+  }
+
+  @Override
+  public void paintComponent(Graphics g1) {
+    Graphics2D g = (Graphics2D) g1;
+
+    pendingPaintings.clear();
+
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+        RenderingHints.VALUE_ANTIALIAS_ON);
+    super.paintComponent(g);
+
+    pendingPaintings.paint(g);
+  }
+}
\ No newline at end of file
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java b/src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java
new file mode 100644 (file)
index 0000000..4fd79a9
--- /dev/null
@@ -0,0 +1,203 @@
+
+/* ====================================================================
+   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;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+
+/**
+ * Sheet Viewer Table Cell Editor -- not commented via javadoc as it
+ * nearly completely consists of overridden methods.
+ *
+ * @author     Jason Height
+ */
+public class SVTableCellEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
+  private static final Color black = getAWTColor(new HSSFColor.BLACK());
+  private static final Color white = getAWTColor(new HSSFColor.WHITE());
+  private Hashtable colors = HSSFColor.getIndexHash();
+
+
+  private HSSFWorkbook wb;
+  private JTextField editor;
+
+  private HSSFCell editorValue;
+
+
+  public SVTableCellEditor(HSSFWorkbook wb) {
+    this.wb = wb;
+    this.editor = new JTextField();
+  }
+
+
+  /**
+   *  Gets the cellEditable attribute of the SVTableCellEditor object
+   *
+   * @return    The cellEditable value
+   */
+  public boolean isCellEditable(java.util.EventObject e) {
+    if (e instanceof MouseEvent) {
+      return ((MouseEvent) e).getClickCount() >= 2;
+    }
+    return false;
+  }
+
+
+  public boolean shouldSelectCell(EventObject anEvent) {
+    return true;
+  }
+
+
+  public boolean startCellEditing(EventObject anEvent) {
+    System.out.println("Start Cell Editing");
+    return true;
+  }
+
+
+  public boolean stopCellEditing() {
+    System.out.println("Stop Cell Editing");
+    fireEditingStopped();
+    return true;
+  }
+
+
+  public void cancelCellEditing() {
+    System.out.println("Cancel Cell Editing");
+    fireEditingCanceled();
+  }
+
+
+  public void actionPerformed(ActionEvent e) {
+    System.out.println("Action performed");
+    stopCellEditing();
+  }
+
+
+  /**
+   *  Gets the cellEditorValue attribute of the SVTableCellEditor object
+   *
+   * @return    The cellEditorValue value
+   */
+  public Object getCellEditorValue() {
+    System.out.println("GetCellEditorValue");
+    //JMH Look at when this method is called. Should it return a HSSFCell?
+    return editor.getText();
+  }
+
+
+  /**
+   *  Gets the tableCellEditorComponent attribute of the SVTableCellEditor object
+   *
+   * @return             The tableCellEditorComponent value
+   */
+  public Component getTableCellEditorComponent(JTable table, Object value,
+      boolean isSelected,
+      int row,
+      int column) {
+    System.out.println("GetTableCellEditorComponent");
+    HSSFCell cell = (HSSFCell) value;
+    if (cell != null) {
+          HSSFCellStyle style = cell.getCellStyle();
+          HSSFFont f = wb.getFontAt(style.getFontIndex());
+          boolean isbold = f.getBoldweight() > HSSFFont.BOLDWEIGHT_NORMAL;
+          boolean isitalics = f.getItalic();
+
+          int fontstyle = Font.PLAIN;
+
+          if (isbold) fontstyle = Font.BOLD;
+          if (isitalics) fontstyle = fontstyle | Font.ITALIC;
+
+          int fontheight = f.getFontHeightInPoints();
+          if (fontheight == 9) fontheight = 10; //fix for stupid ol Windows
+
+          Font font = new Font(f.getFontName(),fontstyle,fontheight);
+          editor.setFont(font);
+
+          if (style.getFillPattern() == HSSFCellStyle.SOLID_FOREGROUND) {
+            editor.setBackground(getAWTColor(style.getFillForegroundColor(), white));
+          } else editor.setBackground(white);
+
+          editor.setForeground(getAWTColor(f.getColor(), black));
+
+
+      //Set the value that is rendered for the cell
+      switch (cell.getCellType()) {
+        case HSSFCell.CELL_TYPE_BLANK:
+          editor.setText("");
+          break;
+        case HSSFCell.CELL_TYPE_BOOLEAN:
+          if (cell.getBooleanCellValue()) {
+            editor.setText("true");
+          } else {
+            editor.setText("false");
+          }
+          break;
+        case HSSFCell.CELL_TYPE_NUMERIC:
+          editor.setText(Double.toString(cell.getNumericCellValue()));
+          break;
+        case HSSFCell.CELL_TYPE_STRING:
+          editor.setText(cell.getRichStringCellValue().getString());
+          break;
+        case HSSFCell.CELL_TYPE_FORMULA:
+        default:
+          editor.setText("?");
+      }
+      switch (style.getAlignment()) {
+        case HSSFCellStyle.ALIGN_LEFT:
+        case HSSFCellStyle.ALIGN_JUSTIFY:
+        case HSSFCellStyle.ALIGN_FILL:
+          editor.setHorizontalAlignment(SwingConstants.LEFT);
+          break;
+        case HSSFCellStyle.ALIGN_CENTER:
+        case HSSFCellStyle.ALIGN_CENTER_SELECTION:
+          editor.setHorizontalAlignment(SwingConstants.CENTER);
+          break;
+        case HSSFCellStyle.ALIGN_GENERAL:
+        case HSSFCellStyle.ALIGN_RIGHT:
+          editor.setHorizontalAlignment(SwingConstants.RIGHT);
+          break;
+        default:
+          editor.setHorizontalAlignment(SwingConstants.LEFT);
+          break;
+      }
+
+    }
+    return editor;
+  }
+
+    /** This method retrieves the AWT Color representation from the colour hash table
+     *
+     */
+    private final Color getAWTColor(int index, Color deflt) {
+      HSSFColor clr = (HSSFColor)colors.get(Integer.valueOf(index));
+      if (clr == null) return deflt;
+      return getAWTColor(clr);
+    }
+
+    private static final Color getAWTColor(HSSFColor clr) {
+      short[] rgb = clr.getTriplet();
+      return new Color(rgb[0],rgb[1],rgb[2]);
+    }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java b/src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java
new file mode 100644 (file)
index 0000000..4b2e634
--- /dev/null
@@ -0,0 +1,274 @@
+
+/* ====================================================================
+   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;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.border.*;
+
+import java.awt.Component;
+import java.awt.Color;
+import java.awt.Rectangle;
+
+import java.io.Serializable;
+import java.text.*;
+
+import org.apache.poi.hssf.usermodel.*;
+
+
+/**
+ * Sheet Viewer Table Cell Render -- not commented via javadoc as it
+ * nearly completely consists of overridden methods.
+ *
+ * @author Andrew C. Oliver
+ */
+public class SVTableCellRenderer extends JLabel
+    implements TableCellRenderer, Serializable
+{
+    protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
+    protected SVBorder cellBorder = new SVBorder();
+
+
+    private HSSFWorkbook wb = null;
+
+    /** This class holds the references to the predefined cell formats.
+     */
+    private class CellFormatter {
+      private Format[] textFormatter;
+
+      private DecimalFormat generalNumberFormat = new DecimalFormat("0");
+
+      public CellFormatter() {
+        textFormatter = new Format[0x31];
+
+        textFormatter[0x01] = new DecimalFormat("0");
+        textFormatter[0x02] = new DecimalFormat("0.00");
+        textFormatter[0x03] = new DecimalFormat("#,##0");
+        textFormatter[0x04] = new DecimalFormat("#,##0.00");
+        textFormatter[0x05] = new DecimalFormat("$#,##0;$#,##0");
+        textFormatter[0x06] = new DecimalFormat("$#,##0;$#,##0");
+        textFormatter[0x07] = new DecimalFormat("$#,##0.00;$#,##0.00");
+        textFormatter[0x08] = new DecimalFormat("$#,##0.00;$#,##0.00");
+        textFormatter[0x09] = new DecimalFormat("0%");
+        textFormatter[0x0A] = new DecimalFormat("0.00%");
+        textFormatter[0x0B] = new DecimalFormat("0.00E0");
+        textFormatter[0x0C] = new SVFractionalFormat("# ?/?");
+        textFormatter[0x0D] = new SVFractionalFormat("# ??/??");
+        textFormatter[0x0E] = new SimpleDateFormat("M/d/yy");
+        textFormatter[0x0F] = new SimpleDateFormat("d-MMM-yy");
+        textFormatter[0x10] = new SimpleDateFormat("d-MMM");
+        textFormatter[0x11] = new SimpleDateFormat("MMM-yy");
+        textFormatter[0x12] = new SimpleDateFormat("h:mm a");
+        textFormatter[0x13] = new SimpleDateFormat("h:mm:ss a");
+        textFormatter[0x14] = new SimpleDateFormat("h:mm");
+        textFormatter[0x15] = new SimpleDateFormat("h:mm:ss");
+        textFormatter[0x16] = new SimpleDateFormat("M/d/yy h:mm");
+        // 0x17 - 0x24 reserved for international and undocumented 0x25, "(#,##0_);(#,##0)"
+        //start at 0x26
+        //jmh need to do colour
+        //"(#,##0_);[Red](#,##0)"
+        textFormatter[0x26] = new DecimalFormat("#,##0;#,##0");
+        //jmh need to do colour
+        //(#,##0.00_);(#,##0.00)
+        textFormatter[0x27] = new DecimalFormat("#,##0.00;#,##0.00");
+        textFormatter[0x28] = new DecimalFormat("#,##0.00;#,##0.00");
+//??        textFormatter[0x29] = new DecimalFormat("_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)");
+//??        textFormatter[0x2A] = new DecimalFormat("_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)");
+//??        textFormatter[0x2B] = new DecimalFormat("_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)");
+//??        textFormatter[0x2C] = new DecimalFormat("_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)");
+        textFormatter[0x2D] = new SimpleDateFormat("mm:ss");
+//??        textFormatter[0x2E] = new SimpleDateFormat("[h]:mm:ss");
+        textFormatter[0x2F] = new SimpleDateFormat("mm:ss.0");
+        textFormatter[0x30] = new DecimalFormat("##0.0E0");
+      }
+
+      public String format(short index, Object value) {
+        if (index == 0)
+          return value.toString();
+        if (textFormatter[index] == null)
+          throw new RuntimeException("Sorry. I cant handle the format code :"+Integer.toHexString(index));
+        return textFormatter[index].format(value);
+      }
+
+      public String format(short index, double value) {
+        if ( index <= 0 )
+          return generalNumberFormat.format(value);
+        if (textFormatter[index] == null)
+          throw new RuntimeException("Sorry. I cant handle the format code :"+Integer.toHexString(index));
+        if (textFormatter[index] instanceof DecimalFormat) {
+          return ((DecimalFormat)textFormatter[index]).format(value);
+        }
+        if (textFormatter[index] instanceof SVFractionalFormat) {
+          return ((SVFractionalFormat)textFormatter[index]).format(value);
+        }
+        throw new RuntimeException("Sorry. I cant handle a non decimal formatter for a decimal value :"+Integer.toHexString(index));
+      }
+
+      public boolean useRedColor(short index, double value) {
+        return (((index == 0x06)||(index == 0x08)||(index == 0x26) || (index == 0x27)) && (value < 0));
+      }
+    }
+
+    private final CellFormatter cellFormatter = new CellFormatter();
+
+    public SVTableCellRenderer(HSSFWorkbook wb) {
+       super();
+       setOpaque(true);
+        setBorder(noFocusBorder);
+        this.wb = wb;
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value,
+                          boolean isSelected, boolean hasFocus, int row, int column) {
+       boolean isBorderSet = false;
+
+        //If the JTables default cell renderer has been setup correctly the
+        //value will be the HSSFCell that we are trying to render
+        HSSFCell c = (HSSFCell)value;
+
+        if (c != null) {
+          HSSFCellStyle s = c.getCellStyle();
+          HSSFFont f = wb.getFontAt(s.getFontIndex());
+          setFont(SVTableUtils.makeFont(f));
+
+          if (s.getFillPattern() == HSSFCellStyle.SOLID_FOREGROUND) {
+            setBackground(SVTableUtils.getAWTColor(s.getFillForegroundColor(), SVTableUtils.white));
+          } else setBackground(SVTableUtils.white);
+
+          setForeground(SVTableUtils.getAWTColor(f.getColor(), SVTableUtils.black));
+
+          cellBorder.setBorder(SVTableUtils.getAWTColor(s.getTopBorderColor(), SVTableUtils.black),
+                               SVTableUtils.getAWTColor(s.getRightBorderColor(), SVTableUtils.black),
+                               SVTableUtils.getAWTColor(s.getBottomBorderColor(), SVTableUtils.black),
+                               SVTableUtils.getAWTColor(s.getLeftBorderColor(), SVTableUtils.black),
+                               s.getBorderTop(), s.getBorderRight(),
+                               s.getBorderBottom(), s.getBorderLeft(),
+                               hasFocus);
+            setBorder(cellBorder);
+            isBorderSet=true;
+
+            //Set the value that is rendered for the cell
+            switch (c.getCellType()) {
+              case HSSFCell.CELL_TYPE_BLANK:
+                setValue("");
+              break;
+              case HSSFCell.CELL_TYPE_BOOLEAN:
+                if (c.getBooleanCellValue()) {
+                  setValue("true");
+                } else {
+                  setValue("false");
+                }
+              break;
+              case HSSFCell.CELL_TYPE_NUMERIC:
+                short format = s.getDataFormat();
+                double numericValue = c.getNumericCellValue();
+                if (cellFormatter.useRedColor(format, numericValue))
+                  setForeground(Color.red);
+                else setForeground(null);
+                setValue(cellFormatter.format(format, c.getNumericCellValue()));
+              break;
+              case HSSFCell.CELL_TYPE_STRING:
+                setValue(c.getRichStringCellValue().getString());
+              break;
+              case HSSFCell.CELL_TYPE_FORMULA:
+              default:
+                setValue("?");
+            }
+            //Set the text alignment of the cell
+            switch (s.getAlignment()) {
+              case HSSFCellStyle.ALIGN_LEFT:
+              case HSSFCellStyle.ALIGN_JUSTIFY:
+              case HSSFCellStyle.ALIGN_FILL:
+                setHorizontalAlignment(SwingConstants.LEFT);
+                break;
+              case HSSFCellStyle.ALIGN_CENTER:
+              case HSSFCellStyle.ALIGN_CENTER_SELECTION:
+                setHorizontalAlignment(SwingConstants.CENTER);
+                break;
+              case HSSFCellStyle.ALIGN_GENERAL:
+              case HSSFCellStyle.ALIGN_RIGHT:
+                setHorizontalAlignment(SwingConstants.RIGHT);
+                break;
+              default:
+                setHorizontalAlignment(SwingConstants.LEFT);
+                break;
+            }
+        } else {
+          setValue("");
+          setBackground(SVTableUtils.white);
+        }
+
+
+       if (hasFocus) {
+            if (!isBorderSet) {
+              //This is the border to paint when there is no border
+              //and the cell has focus
+              cellBorder.setBorder(SVTableUtils.black,
+                                   SVTableUtils.black,
+                                   SVTableUtils.black,
+                                   SVTableUtils.black,
+                                   HSSFCellStyle.BORDER_NONE,
+                                   HSSFCellStyle.BORDER_NONE,
+                                   HSSFCellStyle.BORDER_NONE,
+                                   HSSFCellStyle.BORDER_NONE,
+                                   isSelected);
+              setBorder(cellBorder);
+            }
+           if (table.isCellEditable(row, column)) {
+               setForeground( UIManager.getColor("Table.focusCellForeground") );
+               setBackground( UIManager.getColor("Table.focusCellBackground") );
+           }
+       } else if (!isBorderSet) {
+           setBorder(noFocusBorder);
+       }
+
+       // ---- begin optimization to avoid painting background ----
+       Color back = getBackground();
+       boolean colorMatch = (back != null) && ( back.equals(table.getBackground()) ) && table.isOpaque();
+        setOpaque(!colorMatch);
+       // ---- end optimization to aviod painting background ----
+       return this;
+    }
+
+    public void validate() {}
+
+    public void revalidate() {}
+
+    public void repaint(long tm, int x, int y, int width, int height) {}
+
+    public void repaint(Rectangle r) { }
+
+    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
+       // Strings get interned...
+       if (propertyName=="text") {
+           super.firePropertyChange(propertyName, oldValue, newValue);
+       }
+    }
+
+    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
+
+    /**
+     * Sets the string to either the value or "" if the value is null.
+     *
+     */
+    protected void setValue(Object value) {
+       setText((value == null) ? "" : value.toString());
+    }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVTableModel.java b/src/examples/src/org/apache/poi/hssf/view/SVTableModel.java
new file mode 100644 (file)
index 0000000..170dacb
--- /dev/null
@@ -0,0 +1,87 @@
+
+/* ====================================================================
+   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;
+
+import java.util.Iterator;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+
+/**
+ * Sheet Viewer Table Model - The model for the Sheet Viewer just overrides things.
+ * @author Andrew C. Oliver
+ */
+
+public class SVTableModel extends AbstractTableModel {
+  private HSSFSheet st = null;
+  int maxcol = 0;
+
+  public SVTableModel(HSSFSheet st, int maxcol) {
+    this.st = st;
+    this.maxcol=maxcol;
+  }
+
+  public SVTableModel(HSSFSheet st) {
+    this.st = st;
+    Iterator i = st.rowIterator();
+
+    while (i.hasNext()) {
+      HSSFRow row = (HSSFRow)i.next();
+      if (maxcol < (row.getLastCellNum()+1)) {
+         this.maxcol = row.getLastCellNum();
+      }
+    }
+  }
+
+
+  public int getColumnCount() {
+    return this.maxcol+1;
+  }
+  public Object getValueAt(int row, int col) {
+    HSSFRow r = st.getRow(row);
+    HSSFCell c = null;
+    if (r != null) {
+      c = r.getCell(col);
+    }
+    return c;
+  }
+  public int getRowCount() {
+    return st.getLastRowNum() + 1;
+  }
+
+  public Class getColumnClass(int c) {
+       return HSSFCell.class;
+  }
+
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    return true;
+  }
+
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+    if (aValue != null)
+      System.out.println("SVTableModel.setValueAt. value type = "+aValue.getClass().getName());
+    else System.out.println("SVTableModel.setValueAt. value type = null");
+  }
+
+
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java b/src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java
new file mode 100644 (file)
index 0000000..08ac7b3
--- /dev/null
@@ -0,0 +1,93 @@
+
+/* ====================================================================
+   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;
+
+import java.util.*;
+import java.awt.*;
+import javax.swing.border.*;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.*;
+
+/**
+ * SVTableCell Editor and Renderer helper functions.
+ *
+ * @author     Jason Height
+ */
+public class SVTableUtils {
+  private final static Hashtable colors = HSSFColor.getIndexHash();
+  /**  Description of the Field */
+  public final static Color black = getAWTColor(new HSSFColor.BLACK());
+  /**  Description of the Field */
+  public final static Color white = getAWTColor(new HSSFColor.WHITE());
+  /**  Description of the Field */
+  public static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
+
+
+  /**
+   *  Creates a new font for a specific cell style
+   */
+  public static Font makeFont(HSSFFont font) {
+    boolean isbold = font.getBoldweight() > HSSFFont.BOLDWEIGHT_NORMAL;
+    boolean isitalics = font.getItalic();
+    int fontstyle = Font.PLAIN;
+    if (isbold) {
+      fontstyle = Font.BOLD;
+    }
+    if (isitalics) {
+      fontstyle = fontstyle | Font.ITALIC;
+    }
+
+    int fontheight = font.getFontHeightInPoints();
+    if (fontheight == 9) {
+      //fix for stupid ol Windows
+      fontheight = 10;
+    }
+
+    return new Font(font.getFontName(), fontstyle, fontheight);
+  }
+
+
+  /**
+   * This method retrieves the AWT Color representation from the colour hash table
+   *
+   * @param  index  Description of the Parameter
+   * @param  deflt  Description of the Parameter
+   * @return        The aWTColor value
+   */
+  public final static Color getAWTColor(int index, Color deflt) {
+    HSSFColor clr = (HSSFColor) colors.get(Integer.valueOf(index));
+    if (clr == null) {
+      return deflt;
+    }
+    return getAWTColor(clr);
+  }
+
+
+  /**
+   *  Gets the aWTColor attribute of the SVTableUtils class
+   *
+   * @param  clr  Description of the Parameter
+   * @return      The aWTColor value
+   */
+  public final static Color getAWTColor(HSSFColor clr) {
+    short[] rgb = clr.getTriplet();
+    return new Color(rgb[0], rgb[1], rgb[2]);
+  }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SViewer.java b/src/examples/src/org/apache/poi/hssf/view/SViewer.java
new file mode 100644 (file)
index 0000000..de2cfb1
--- /dev/null
@@ -0,0 +1,172 @@
+
+/* ====================================================================
+   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;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.io.*;
+import javax.swing.*;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Sheet Viewer - Views XLS files via HSSF.  Can be used as an applet with
+ * filename="" or as a applications (pass the filename as the first parameter).
+ * Or you can pass it a URL in a "url" parameter when run as an applet or just
+ * that first parameter must start with http:// and it will guess its a url. I
+ * only tested it as an applet though, so it probably won't work...you fix it.
+ *
+ * @author Andrew C. Oliver
+ * @author Jason Height
+ */
+public class SViewer extends JApplet {
+  private SViewerPanel panel;
+  boolean isStandalone = false;
+  String filename = null;
+
+  /**Get a parameter value*/
+  public String getParameter(String key, String def) {
+    return isStandalone ? System.getProperty(key, def) :
+      (getParameter(key) != null ? getParameter(key) : def);
+  }
+
+  /**Construct the applet*/
+  public SViewer() {
+  }
+
+  /**Initialize the applet*/
+  public void init() {
+    try {
+      jbInit();
+    }
+    catch(Exception e) {
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  /**Component initialization*/
+  private void jbInit() throws Exception {
+    InputStream i = null;
+    boolean isurl = false;
+    if (filename == null) filename = getParameter("filename");
+
+    if (filename == null || filename.substring(0,7).equals("http://")) {
+      isurl = true;
+      if (filename == null) filename = getParameter("url");
+      i = getXLSFromURL(filename);
+    }
+
+    HSSFWorkbook wb = null;
+    if (isurl) {
+      wb = constructWorkbook(i);
+    } else {
+      wb = constructWorkbook(filename);
+    }
+    panel = new SViewerPanel(wb, false);
+    getContentPane().setLayout(new BorderLayout());
+    getContentPane().add(panel, BorderLayout.CENTER);
+  }
+
+  private HSSFWorkbook constructWorkbook(String filename) throws FileNotFoundException, IOException {
+    HSSFWorkbook wb = null;
+      FileInputStream in = new FileInputStream(filename);
+      wb = new HSSFWorkbook(in);
+      in.close();
+    return wb;
+  }
+
+  private HSSFWorkbook constructWorkbook(InputStream in) throws IOException {
+    HSSFWorkbook wb = null;
+
+      wb = new HSSFWorkbook(in);
+      in.close();
+    return wb;
+  }
+
+  /**Start the applet*/
+  public void start() {
+  }
+  /**Stop the applet*/
+  public void stop() {
+  }
+  /**Destroy the applet*/
+  public void destroy() {
+  }
+  /**Get Applet information*/
+  public String getAppletInfo() {
+    return "Applet Information";
+  }
+  /**Get parameter info*/
+  public String[][] getParameterInfo() {
+    return null;
+  }
+
+  /**
+   * opens a url and returns an inputstream
+   *
+   */
+  private InputStream getXLSFromURL(String urlstring) throws MalformedURLException, IOException {
+    URL url = new URL(urlstring);
+    URLConnection uc = url.openConnection();
+    String field = uc.getHeaderField(0);
+    for (int i=0;field != null; i++) {
+      System.out.println(field);
+      field = uc.getHeaderField(i);
+  }
+    BufferedInputStream is = new BufferedInputStream(uc.getInputStream());
+    return is;
+  }
+
+
+  /**Main method*/
+  public static void main(String[] args) {
+    if(args.length < 1) {
+      throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
+    }
+
+    SViewer applet = new SViewer();
+    applet.isStandalone = true;
+    applet.filename = args[0];
+    Frame frame;
+    frame = new Frame() {
+      protected void processWindowEvent(WindowEvent e) {
+        super.processWindowEvent(e);
+        if (e.getID() == WindowEvent.WINDOW_CLOSING) {
+          System.exit(0);
+        }
+      }
+      public synchronized void setTitle(String title) {
+        super.setTitle(title);
+        enableEvents(AWTEvent.WINDOW_EVENT_MASK);
+      }
+    };
+    frame.setTitle("Applet Frame");
+    frame.add(applet, BorderLayout.CENTER);
+    applet.init();
+    applet.start();
+    frame.setSize(400,320);
+    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
+    frame.setVisible(true);
+  }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java b/src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java
new file mode 100644 (file)
index 0000000..5fe5962
--- /dev/null
@@ -0,0 +1,292 @@
+/* ====================================================================
+   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;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.*;
+
+/**
+ * This class presents the sheets to the user.
+ *
+ *
+ * @author Andrew C. Oliver
+ * @author Jason Height
+ */
+public class SViewerPanel extends JPanel {
+  /** This field is the magic number to convert from a Character width to a
+   *  java pixel width.
+   *
+   * When the "normal" font size in a workbook changes, this effects all
+   * of the heights and widths. Unfortunately there is no way to retrieve this
+   * information, hence the MAGIC number.
+   *
+   * This number may only work for the normal style font size of Arial size 10.
+   *
+   */
+  private static final int magicCharFactor = 7;
+  /** Reference to the wookbook that is being displayed*/
+  /* package */ HSSFWorkbook wb;
+  /** Reference to the tabs component*/
+  /* package */ JTabbedPane sheetPane;
+  /** Reference to the cell renderer that is used to render all cells*/
+  private SVTableCellRenderer cellRenderer;
+  /** Reference to the cell editor that is used to edit all cells.
+   *  Only constructed if editing is allowed
+   */
+  private SVTableCellEditor cellEditor;
+  /** Flag indicating if editing is allowed. Otherwise the viewer is in
+   *  view only mode.
+   */
+  private boolean allowEdits;
+
+  /**Construct the representation of the workbook*/
+  public SViewerPanel(HSSFWorkbook wb, boolean allowEdits) {
+    this.wb = wb;
+    this.allowEdits = allowEdits;
+
+    initialiseGui();
+  }
+
+  private void initialiseGui() {
+    cellRenderer = new SVTableCellRenderer(this.wb);
+    if (allowEdits)
+      cellEditor = new SVTableCellEditor(this.wb);
+
+    //Initialise the Panel
+    sheetPane = new JTabbedPane(JTabbedPane.BOTTOM);
+
+    if (allowEdits)
+      sheetPane.addMouseListener(createTabListener());
+    int sheetCount = wb.getNumberOfSheets();
+    for (int i=0; i<sheetCount;i++) {
+      String sheetName = wb.getSheetName(i);
+      //Add the new sheet to the tabbed pane
+      sheetPane.addTab(sheetName, makeSheetView(wb.getSheetAt(i)));
+    }
+    setLayout(new BorderLayout());
+    add(sheetPane, BorderLayout.CENTER);
+  }
+
+  protected JComponent makeSheetView(HSSFSheet sheet) {
+    JTable sheetView = new JTable(new SVTableModel(sheet));
+    sheetView.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+    sheetView.setDefaultRenderer(HSSFCell.class, cellRenderer);
+    if (allowEdits)
+      sheetView.setDefaultEditor(HSSFCell.class, cellEditor);
+    JTableHeader header = sheetView.getTableHeader();
+    //Dont allow column reordering
+    header.setReorderingAllowed(false);
+    //Only allow column resizing if editing is allowed
+    header.setResizingAllowed(allowEdits);
+
+    //Set the columns the correct size
+    TableColumnModel columns = sheetView.getColumnModel();
+    for (int i=0; i< columns.getColumnCount(); i++) {
+      TableColumn column = columns.getColumn(i);
+      int width = sheet.getColumnWidth(i);
+      //256 is because the width is in 256ths of a character
+      column.setPreferredWidth(width/256*magicCharFactor);
+    }
+
+    //Set the rows to the correct size
+    int rows = sheet.getPhysicalNumberOfRows();
+    Insets insets = cellRenderer.getInsets();
+    //Need to include the insets in the calculation of the row height to use.
+    int extraHeight = insets.bottom+insets.top;
+    for (int i=0; i< rows; i++) {
+      HSSFRow row = sheet.getRow(i);
+      if (row == null) {
+        sheetView.setRowHeight(i, (int)sheet.getDefaultRowHeightInPoints()+extraHeight);
+      } else {
+        sheetView.setRowHeight(i, (int)row.getHeightInPoints()+extraHeight);
+      }
+    }
+
+    //Add the row header to the sheet
+    SVRowHeader rowHeader = new SVRowHeader(sheet, sheetView, extraHeight);
+    JScrollPane scroll = new JScrollPane( sheetView );
+    scroll.setRowHeaderView(rowHeader);
+    return scroll;
+  }
+
+  public void paint(Graphics g) {
+    //JMH I am only overriding this to get a picture of the time taken to paint
+    long start = System.currentTimeMillis();
+    super.paint(g);
+    long elapsed = System.currentTimeMillis()-start;
+    System.out.println("Paint time = "+elapsed);
+  }
+
+  protected MouseListener createTabListener() {
+    return new TabListener();
+  }
+
+  /** This class defines the default MouseListener that listens to
+   *  mouse events in the tabbed pane
+   *
+   *  The default is to popup a menu when the event occurs over a tab
+   */
+  private class TabListener implements MouseListener {
+    public JPopupMenu popup;
+    public TabListener() {
+      popup = new JPopupMenu("Sheet");
+      popup.add(createInsertSheetAction());
+      popup.add(createDeleteSheetAction());
+      popup.add(createRenameSheetAction());
+    }
+
+    protected Action createInsertSheetAction() {
+      return new InsertSheetAction();
+    }
+
+    protected Action createDeleteSheetAction() {
+      return new DeleteSheetAction();
+    }
+
+    protected Action createRenameSheetAction() {
+      return new RenameSheetAction();
+    }
+
+
+    /** This method will display the popup if the mouseevent is a popup event
+     *  and the event occurred over a tab
+     */
+    protected void checkPopup(MouseEvent e) {
+      if (e.isPopupTrigger()) {
+        int tab = sheetPane.getUI().tabForCoordinate(sheetPane, e.getX(), e.getY());
+        if (tab != -1) {
+          popup.show(sheetPane, e.getX(), e.getY());
+        }
+      }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+      checkPopup(e);
+    }
+
+    public void mousePressed(MouseEvent e) {
+      checkPopup(e);
+    }
+
+    public void mouseReleased(MouseEvent e) {
+      checkPopup(e);
+    }
+
+    public void mouseEntered(MouseEvent e) {}
+    public void mouseExited(MouseEvent e) {}
+  }
+
+  /** This class defines the action that is performed when the sheet is renamed*/
+  private class RenameSheetAction extends AbstractAction {
+    public RenameSheetAction() {
+      super("Rename");
+    }
+
+    public void actionPerformed(ActionEvent e) {
+      int tabIndex = sheetPane.getSelectedIndex();
+      if (tabIndex != -1) {
+        String newSheetName = JOptionPane.showInputDialog(sheetPane, "Enter a new Sheetname", "Rename Sheet", JOptionPane.QUESTION_MESSAGE);
+        if (newSheetName != null) {
+          wb.setSheetName(tabIndex, newSheetName);
+          sheetPane.setTitleAt(tabIndex, newSheetName);
+        }
+      }
+    }
+  }
+
+  /** This class defines the action that is performed when a sheet is inserted*/
+  private class InsertSheetAction extends AbstractAction {
+    public InsertSheetAction() {
+      super("Insert");
+    }
+
+    public void actionPerformed(ActionEvent e) {
+      //Create a new sheet then search for the sheet and make sure that the
+      //sheetPane shows it.
+      HSSFSheet newSheet = wb.createSheet();
+      for (int i=0; i<wb.getNumberOfSheets();i++) {
+        HSSFSheet sheet = wb.getSheetAt(i);
+        if (newSheet == sheet) {
+          sheetPane.insertTab(wb.getSheetName(i), null, makeSheetView(sheet), null, i);
+        }
+      }
+    }
+  }
+
+  /** This class defines the action that is performed when the sheet is deleted*/
+  private class DeleteSheetAction extends AbstractAction {
+    public DeleteSheetAction() {
+      super("Delete");
+    }
+
+    public void actionPerformed(ActionEvent e) {
+      int tabIndex = sheetPane.getSelectedIndex();
+      if (tabIndex != -1) {
+        if (JOptionPane.showConfirmDialog(sheetPane, "Are you sure that you want to delete the selected sheet", "Delete Sheet?", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
+          wb.removeSheetAt(tabIndex);
+          sheetPane.remove(tabIndex);
+        }
+      }
+    }
+  }
+
+  public boolean isEditable() {
+    return allowEdits;
+  }
+
+  /**Main method*/
+  public static void main(String[] args) {
+    if(args.length < 1) {
+      throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
+    }
+    try {
+      FileInputStream in = new FileInputStream(args[0]);
+      HSSFWorkbook wb = new HSSFWorkbook(in);
+      in.close();
+
+      SViewerPanel p = new SViewerPanel(wb, true);
+      JFrame frame;
+      frame = new JFrame() {
+        protected void processWindowEvent(WindowEvent e) {
+          super.processWindowEvent(e);
+          if (e.getID() == WindowEvent.WINDOW_CLOSING) {
+            System.exit(0);
+          }
+        }
+        public synchronized void setTitle(String title) {
+          super.setTitle(title);
+          enableEvents(AWTEvent.WINDOW_EVENT_MASK);
+        }
+      };
+      frame.setTitle("Viewer Frame");
+      frame.getContentPane().add(p, BorderLayout.CENTER);
+      frame.setSize(800,640);
+      Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+      frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
+      frame.setVisible(true);
+    } catch (IOException ex) {
+      ex.printStackTrace();
+      System.exit(1);
+    }
+  }
+}
diff --git a/src/examples/src/org/apache/poi/hssf/view/brush/BasicBrush.java b/src/examples/src/org/apache/poi/hssf/view/brush/BasicBrush.java
new file mode 100644 (file)
index 0000000..db36b83
--- /dev/null
@@ -0,0 +1,72 @@
+/* ====================================================================
+   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 is a basic brush that just draws the line with the given parameters.
+ * This is a {@link BasicStroke} object that can be used as a {@link Brush}.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ * @see BasicStroke
+ */
+public class BasicBrush extends BasicStroke implements Brush {
+    /**
+     * Creates a new basic brush with the given width. Invokes {@link
+     * BasicStroke#BasicStroke(float)}
+     *
+     * @param width The brush width.
+     *
+     * @see BasicStroke#BasicStroke(float)
+     */
+    public BasicBrush(float width) {
+        super(width);
+    }
+
+    /**
+     * Creates a new basic brush with the given width, cap, and join.  Invokes
+     * {@link BasicStroke#BasicStroke(float,int,int)}
+     *
+     * @param width The brush width.
+     * @param cap   The capping style.
+     * @param join  The join style.
+     *
+     * @see BasicStroke#BasicStroke(float, int, int)
+     */
+    public BasicBrush(float width, int cap, int join) {
+        super(width, cap, join);
+    }
+
+    /**
+     * Creates a new basic brush with the given parameters.  Invokes {@link
+     * BasicStroke#BasicStroke(float,int,int,float,float[],float)} with a miter
+     * limit of 11 (the normal default value).
+     *
+     * @param width   The brush width.
+     * @param cap     The capping style.
+     * @param join    The join style.
+     * @param dashes  The dash intervals.
+     * @param dashPos The intial dash position in the dash intervals.
+     *
+     * @see BasicStroke#BasicStroke(float, int, int, float, float[], float)
+     */
+    public BasicBrush(float width, int cap, int join, float[] dashes,
+            int dashPos) {
+        super(width, cap, join, 11.0f, dashes, dashPos);
+    }
+}
\ No newline at end of file
diff --git a/src/examples/src/org/apache/poi/hssf/view/brush/Brush.java b/src/examples/src/org/apache/poi/hssf/view/brush/Brush.java
new file mode 100644 (file)
index 0000000..30786a6
--- /dev/null
@@ -0,0 +1,14 @@
+package org.apache.poi.hssf.view.brush;
+
+import java.awt.*;
+
+/**
+ * This is the type you must implement to create a brush that will be used for a
+ * spreadsheet border.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public interface Brush extends Stroke {
+    /** Returns the width of the brush. */
+    float getLineWidth();
+}
\ No newline at end of file
diff --git a/src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java b/src/examples/src/org/apache/poi/hssf/view/brush/DoubleStroke.java
new file mode 100644 (file)
index 0000000..126214b
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java b/src/examples/src/org/apache/poi/hssf/view/brush/PendingPaintings.java
new file mode 100644 (file)
index 0000000..3b95c05
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/hssf/view/brush/package.html b/src/examples/src/org/apache/poi/hssf/view/brush/package.html
new file mode 100644 (file)
index 0000000..dc4b833
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java b/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java
new file mode 100644 (file)
index 0000000..1e235f9
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java b/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java
new file mode 100644 (file)
index 0000000..1d4c2a5
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java b/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java
new file mode 100644 (file)
index 0000000..b5480cf
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java b/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java
new file mode 100644 (file)
index 0000000..36fca3a
--- /dev/null
@@ -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
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css b/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css
new file mode 100644 (file)
index 0000000..e428918
--- /dev/null
@@ -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;
+}
diff --git a/src/examples/src/org/apache/poi/ss/examples/html/package.html b/src/examples/src/org/apache/poi/ss/examples/html/package.html
new file mode 100644 (file)
index 0000000..b041119
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellDateFormatter.java b/src/java/org/apache/poi/ss/format/CellDateFormatter.java
new file mode 100644 (file)
index 0000000..5007305
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java b/src/java/org/apache/poi/ss/format/CellElapsedFormatter.java
new file mode 100644 (file)
index 0000000..07ebadf
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormat.java b/src/java/org/apache/poi/ss/format/CellFormat.java
new file mode 100644 (file)
index 0000000..c7c22f0
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormatCondition.java b/src/java/org/apache/poi/ss/format/CellFormatCondition.java
new file mode 100644 (file)
index 0000000..23fd2f2
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormatPart.java b/src/java/org/apache/poi/ss/format/CellFormatPart.java
new file mode 100644 (file)
index 0000000..cedc94f
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormatResult.java b/src/java/org/apache/poi/ss/format/CellFormatResult.java
new file mode 100644 (file)
index 0000000..3c45de3
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormatType.java b/src/java/org/apache/poi/ss/format/CellFormatType.java
new file mode 100644 (file)
index 0000000..363af1c
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellFormatter.java b/src/java/org/apache/poi/ss/format/CellFormatter.java
new file mode 100644 (file)
index 0000000..a125181
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java b/src/java/org/apache/poi/ss/format/CellGeneralFormatter.java
new file mode 100644 (file)
index 0000000..e2adc61
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellNumberFormatter.java b/src/java/org/apache/poi/ss/format/CellNumberFormatter.java
new file mode 100644 (file)
index 0000000..8276afd
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/CellTextFormatter.java b/src/java/org/apache/poi/ss/format/CellTextFormatter.java
new file mode 100644 (file)
index 0000000..ebefa98
--- /dev/null
@@ -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
diff --git a/src/java/org/apache/poi/ss/format/package.html b/src/java/org/apache/poi/ss/format/package.html
new file mode 100644 (file)
index 0000000..d5ab99f
--- /dev/null
@@ -0,0 +1,3 @@
+<body>
+This package contains classes that implement cell formatting
+</body>
diff --git a/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java b/src/ooxml/testcases/org/apache/poi/ss/format/TestCellFormatPart.java
new file mode 100644 (file)
index 0000000..bd51836
--- /dev/null
@@ -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
diff --git a/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java b/src/testcases/org/apache/poi/ss/format/CellFormatTestBase.java
new file mode 100644 (file)
index 0000000..6b5c61b
--- /dev/null
@@ -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
diff --git a/src/testcases/org/apache/poi/ss/format/TestCellFormat.java b/src/testcases/org/apache/poi/ss/format/TestCellFormat.java
new file mode 100644 (file)
index 0000000..3e0b150
--- /dev/null
@@ -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
diff --git a/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java b/src/testcases/org/apache/poi/ss/format/TestCellFormatCondition.java
new file mode 100644 (file)
index 0000000..f1ede11
--- /dev/null
@@ -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
diff --git a/test-data/spreadsheet/DateFormatTests.xlsx b/test-data/spreadsheet/DateFormatTests.xlsx
new file mode 100644 (file)
index 0000000..a6099be
Binary files /dev/null and b/test-data/spreadsheet/DateFormatTests.xlsx differ
diff --git a/test-data/spreadsheet/ElapsedFormatTests.xlsx b/test-data/spreadsheet/ElapsedFormatTests.xlsx
new file mode 100644 (file)
index 0000000..95a093e
Binary files /dev/null and b/test-data/spreadsheet/ElapsedFormatTests.xlsx differ
diff --git a/test-data/spreadsheet/FormatChoiceTests.xls b/test-data/spreadsheet/FormatChoiceTests.xls
new file mode 100644 (file)
index 0000000..1816763
Binary files /dev/null and b/test-data/spreadsheet/FormatChoiceTests.xls differ
diff --git a/test-data/spreadsheet/FormatChoiceTests.xlsx b/test-data/spreadsheet/FormatChoiceTests.xlsx
new file mode 100644 (file)
index 0000000..64b5a16
Binary files /dev/null and b/test-data/spreadsheet/FormatChoiceTests.xlsx differ
diff --git a/test-data/spreadsheet/FormatConditionTests.xlsx b/test-data/spreadsheet/FormatConditionTests.xlsx
new file mode 100644 (file)
index 0000000..2262258
Binary files /dev/null and b/test-data/spreadsheet/FormatConditionTests.xlsx differ
diff --git a/test-data/spreadsheet/GeneralFormatTests.xlsx b/test-data/spreadsheet/GeneralFormatTests.xlsx
new file mode 100644 (file)
index 0000000..4f34dad
Binary files /dev/null and b/test-data/spreadsheet/GeneralFormatTests.xlsx differ
diff --git a/test-data/spreadsheet/NumberFormatApproxTests.xlsx b/test-data/spreadsheet/NumberFormatApproxTests.xlsx
new file mode 100644 (file)
index 0000000..db40604
Binary files /dev/null and b/test-data/spreadsheet/NumberFormatApproxTests.xlsx differ
diff --git a/test-data/spreadsheet/NumberFormatTests.xlsx b/test-data/spreadsheet/NumberFormatTests.xlsx
new file mode 100644 (file)
index 0000000..709a11e
Binary files /dev/null and b/test-data/spreadsheet/NumberFormatTests.xlsx differ
diff --git a/test-data/spreadsheet/TextFormatTests.xlsx b/test-data/spreadsheet/TextFormatTests.xlsx
new file mode 100644 (file)
index 0000000..84d6f48
Binary files /dev/null and b/test-data/spreadsheet/TextFormatTests.xlsx differ