git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@942809 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_7_BETA1
@@ -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"> |
@@ -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> |
@@ -16,7 +16,7 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.awt.*; | |||
@@ -17,7 +17,7 @@ | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.text.*; | |||
@@ -18,7 +18,7 @@ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.awt.*; | |||
import javax.swing.*; |
@@ -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); | |||
} | |||
} |
@@ -16,7 +16,7 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.awt.*; | |||
import java.awt.event.*; |
@@ -17,7 +17,7 @@ | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import javax.swing.*; | |||
import javax.swing.table.TableCellRenderer; |
@@ -18,7 +18,7 @@ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.util.Iterator; | |||
import javax.swing.table.*; |
@@ -16,7 +16,7 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.util.*; | |||
import java.awt.*; |
@@ -18,7 +18,7 @@ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.awt.*; | |||
import java.awt.event.*; |
@@ -15,7 +15,7 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hssf.contrib.view; | |||
package org.apache.poi.hssf.view; | |||
import java.awt.*; | |||
import java.awt.event.*; |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,4 @@ | |||
This package contains some brushes that are used when drawing borders for Excel | |||
cells. | |||
@author Ken Arnold, Industrious Media LLC |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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>◊</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 = " "; | |||
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 = " "; | |||
} | |||
} | |||
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 ""; | |||
} | |||
} |
@@ -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]); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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. |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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>"<"</tt>, | |||
* <tt>"<="</tt>, <tt>">"</tt>, <tt>">="</tt>, | |||
* <tt>"="</tt>, <tt>"=="</tt>, <tt>"!="</tt>, or | |||
* <tt>"<>"</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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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 + '"'; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
<body> | |||
This package contains classes that implement cell formatting | |||
</body> |
@@ -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()); | |||
} | |||
} |
@@ -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 + "\"]"); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |