diff --git a/src/contrib/src/org/apache/poi/hssf/usermodel/contrib/HSSFRegionUtil.java b/src/contrib/src/org/apache/poi/hssf/usermodel/contrib/HSSFRegionUtil.java index 53e176c07..be35b0425 100644 --- a/src/contrib/src/org/apache/poi/hssf/usermodel/contrib/HSSFRegionUtil.java +++ b/src/contrib/src/org/apache/poi/hssf/usermodel/contrib/HSSFRegionUtil.java @@ -21,197 +21,257 @@ import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; - +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.Region; /** - * Various utility functions that make working with a region of cells easier. - * - *@author Eric Pugh epugh@upstate.com - *@since July 29, 2002 + * Various utility functions that make working with a region of cells easier. + * + * @author Eric Pugh epugh@upstate.com */ -public final class HSSFRegionUtil -{ +public final class HSSFRegionUtil { - /** Constructor for the HSSFRegionUtil object */ - private HSSFRegionUtil() { - // no instances of this class - } + private HSSFRegionUtil() { + // no instances of this class + } + /** + * For setting the same property on many cells to the same value + */ + private static final class CellPropertySetter { - /** - * Sets the left border for a region of cells by manipulating the cell style - * of the individual cells on the left - * - *@param border The new border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setBorderLeft( short border, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int rowStart = region.getRowFrom(); - int rowEnd = region.getRowTo(); - int column = region.getColumnFrom(); + private final HSSFWorkbook _workbook; + private final String _propertyName; + private final Short _propertyValue; - for ( int i = rowStart; i <= rowEnd; i++ ) { - HSSFRow row = HSSFCellUtil.getRow( i, sheet ); - HSSFCell cell = HSSFCellUtil.getCell( row, column ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.BORDER_LEFT, new Short( border ) ); - } - } + public CellPropertySetter(HSSFWorkbook workbook, String propertyName, int value) { + _workbook = workbook; + _propertyName = propertyName; + _propertyValue = new Short((short)value); + } + public void setProperty(HSSFRow row, int column) { + HSSFCell cell = HSSFCellUtil.getCell(row, column); + HSSFCellUtil.setCellStyleProperty(cell, _workbook, _propertyName, _propertyValue); + } + } - /** - * Sets the leftBorderColor attribute of the HSSFRegionUtil object - * - *@param color The color of the border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setLeftBorderColor( short color, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int rowStart = region.getRowFrom(); - int rowEnd = region.getRowTo(); - int column = region.getColumnFrom(); + private static CellRangeAddress toCRA(Region region) { + return Region.convertToCellRangeAddress(region); + } - for ( int i = rowStart; i <= rowEnd; i++ ) { - HSSFRow row = HSSFCellUtil.getRow( i, sheet ); - HSSFCell cell = HSSFCellUtil.getCell( row, column ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.LEFT_BORDER_COLOR, new Short( color ) ); - } - } + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setBorderLeft(short border, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setBorderLeft(border, toCRA(region), sheet, workbook); + } + /** + * Sets the left border for a region of cells by manipulating the cell style + * of the individual cells on the left + * + * @param border The new border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setBorderLeft(int border, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int rowStart = region.getFirstRow(); + int rowEnd = region.getLastRow(); + int column = region.getFirstColumn(); - /** - * Sets the borderRight attribute of the HSSFRegionUtil object - * - *@param border The new border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setBorderRight( short border, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int rowStart = region.getRowFrom(); - int rowEnd = region.getRowTo(); - int column = region.getColumnTo(); + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.BORDER_LEFT, border); + for (int i = rowStart; i <= rowEnd; i++) { + cps.setProperty(HSSFCellUtil.getRow(i, sheet), column); + } + } - for ( int i = rowStart; i <= rowEnd; i++ ) { - HSSFRow row = HSSFCellUtil.getRow( i, sheet ); - HSSFCell cell = HSSFCellUtil.getCell( row, column ); + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setLeftBorderColor(short color, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setLeftBorderColor(color, toCRA(region), sheet, workbook); + } + /** + * Sets the leftBorderColor attribute of the HSSFRegionUtil object + * + * @param color The color of the border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setLeftBorderColor(int color, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int rowStart = region.getFirstRow(); + int rowEnd = region.getLastRow(); + int column = region.getFirstColumn(); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.BORDER_RIGHT, new Short( border ) ); - } - } + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.LEFT_BORDER_COLOR, color); + for (int i = rowStart; i <= rowEnd; i++) { + cps.setProperty(HSSFCellUtil.getRow(i, sheet), column); + } + } - /** - * Sets the rightBorderColor attribute of the HSSFRegionUtil object - * - *@param color The color of the border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setRightBorderColor( short color, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int rowStart = region.getRowFrom(); - int rowEnd = region.getRowTo(); - int column = region.getColumnTo(); + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setBorderRight(short border, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setBorderRight(border, toCRA(region), sheet, workbook); + } + /** + * Sets the borderRight attribute of the HSSFRegionUtil object + * + * @param border The new border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setBorderRight(int border, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int rowStart = region.getFirstRow(); + int rowEnd = region.getLastRow(); + int column = region.getLastColumn(); - for ( int i = rowStart; i <= rowEnd; i++ ) { - HSSFRow row = HSSFCellUtil.getRow( i, sheet ); - HSSFCell cell = HSSFCellUtil.getCell( row, column ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.RIGHT_BORDER_COLOR, new Short( color ) ); - } - } + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.BORDER_RIGHT, border); + for (int i = rowStart; i <= rowEnd; i++) { + cps.setProperty(HSSFCellUtil.getRow(i, sheet), column); + } + } - /** - * Sets the borderBottom attribute of the HSSFRegionUtil object - * - *@param border The new border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setBorderBottom( short border, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int colStart = region.getColumnFrom(); - int colEnd = region.getColumnTo(); - int rowIndex = region.getRowTo(); - HSSFRow row = HSSFCellUtil.getRow( rowIndex, sheet ); - for ( int i = colStart; i <= colEnd; i++ ) { + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setRightBorderColor(short color, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setRightBorderColor(color, toCRA(region), sheet, workbook); + } + /** + * Sets the rightBorderColor attribute of the HSSFRegionUtil object + * + * @param color The color of the border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setRightBorderColor(int color, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int rowStart = region.getFirstRow(); + int rowEnd = region.getLastRow(); + int column = region.getLastColumn(); - HSSFCell cell = HSSFCellUtil.getCell( row, i ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.BORDER_BOTTOM, new Short( border ) ); - } - } + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.RIGHT_BORDER_COLOR, color); + for (int i = rowStart; i <= rowEnd; i++) { + cps.setProperty(HSSFCellUtil.getRow(i, sheet), column); + } + } - /** - * Sets the bottomBorderColor attribute of the HSSFRegionUtil object - * - *@param color The color of the border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setBottomBorderColor( short color, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int colStart = region.getColumnFrom(); - int colEnd = region.getColumnTo(); - int rowIndex = region.getRowTo(); - HSSFRow row = HSSFCellUtil.getRow( rowIndex, sheet ); - for ( int i = colStart; i <= colEnd; i++ ) { - HSSFCell cell = HSSFCellUtil.getCell( row, i ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.BOTTOM_BORDER_COLOR, new Short( color ) ); - } - } + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setBorderBottom(short border, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setBorderBottom(border, toCRA(region), sheet, workbook); + } + /** + * Sets the borderBottom attribute of the HSSFRegionUtil object + * + * @param border The new border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setBorderBottom(int border, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int colStart = region.getFirstColumn(); + int colEnd = region.getLastColumn(); + int rowIndex = region.getLastRow(); + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.BORDER_BOTTOM, border); + HSSFRow row = HSSFCellUtil.getRow(rowIndex, sheet); + for (int i = colStart; i <= colEnd; i++) { + cps.setProperty(row, i); + } + } - /** - * Sets the borderBottom attribute of the HSSFRegionUtil object - * - *@param border The new border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setBorderTop( short border, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int colStart = region.getColumnFrom(); - int colEnd = region.getColumnTo(); - int rowIndex = region.getRowFrom(); - HSSFRow row = HSSFCellUtil.getRow( rowIndex, sheet ); - for ( int i = colStart; i <= colEnd; i++ ) { + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setBottomBorderColor(short color, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setBottomBorderColor(color, toCRA(region), sheet, workbook); + } + /** + * Sets the bottomBorderColor attribute of the HSSFRegionUtil object + * + * @param color The color of the border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setBottomBorderColor(int color, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int colStart = region.getFirstColumn(); + int colEnd = region.getLastColumn(); + int rowIndex = region.getLastRow(); + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.BOTTOM_BORDER_COLOR, color); + HSSFRow row = HSSFCellUtil.getRow(rowIndex, sheet); + for (int i = colStart; i <= colEnd; i++) { + cps.setProperty(row, i); + } + } - HSSFCell cell = HSSFCellUtil.getCell( row, i ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.BORDER_TOP, new Short( border ) ); - } - } + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setBorderTop(short border, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setBorderTop(border, toCRA(region), sheet, workbook); + } + /** + * Sets the borderBottom attribute of the HSSFRegionUtil object + * + * @param border The new border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setBorderTop(int border, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int colStart = region.getFirstColumn(); + int colEnd = region.getLastColumn(); + int rowIndex = region.getFirstRow(); + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.BORDER_TOP, border); + HSSFRow row = HSSFCellUtil.getRow(rowIndex, sheet); + for (int i = colStart; i <= colEnd; i++) { + cps.setProperty(row, i); + } + } - /** - * Sets the topBorderColor attribute of the HSSFRegionUtil object - * - *@param color The color of the border - *@param region The region that should have the border - *@param workbook The workbook that the region is on. - *@param sheet The sheet that the region is on. - */ - public static void setTopBorderColor( short color, Region region, HSSFSheet sheet, HSSFWorkbook workbook ) - { - int colStart = region.getColumnFrom(); - int colEnd = region.getColumnTo(); - int rowIndex = region.getRowFrom(); - HSSFRow row = HSSFCellUtil.getRow( rowIndex, sheet ); - for ( int i = colStart; i <= colEnd; i++ ) { - HSSFCell cell = HSSFCellUtil.getCell( row, i ); - HSSFCellUtil.setCellStyleProperty( - cell, workbook, HSSFCellUtil.TOP_BORDER_COLOR, new Short( color ) ); - } - } + /** + * @deprecated (Aug 2008) use {@link CellRangeAddress} instead of {@link Region} + */ + public static void setTopBorderColor(short color, Region region, HSSFSheet sheet, + HSSFWorkbook workbook) { + setTopBorderColor(color, toCRA(region), sheet, workbook); + } + /** + * Sets the topBorderColor attribute of the HSSFRegionUtil object + * + * @param color The color of the border + * @param region The region that should have the border + * @param workbook The workbook that the region is on. + * @param sheet The sheet that the region is on. + */ + public static void setTopBorderColor(int color, CellRangeAddress region, HSSFSheet sheet, + HSSFWorkbook workbook) { + int colStart = region.getFirstColumn(); + int colEnd = region.getLastColumn(); + int rowIndex = region.getFirstRow(); + CellPropertySetter cps = new CellPropertySetter(workbook, HSSFCellUtil.TOP_BORDER_COLOR, color); + HSSFRow row = HSSFCellUtil.getRow(rowIndex, sheet); + for (int i = colStart; i <= colEnd; i++) { + cps.setProperty(row, i); + } + } } - diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index d7f6d1732..8e167940b 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -52,6 +52,11 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Support for Headers / Footers in HSLF + 44953 - Extensive fixes for data validation + 45519 - Fixed to keep datavalidation records together + Support for creating new HSLF CurrentUserAtoms + 45466 - Partial support for removing excel comments (won't work for all excel versions yet) 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does 45414 - Don't add too many UncalcedRecords to sheets with charts in them diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 208799a00..790390b02 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -49,6 +49,11 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Support for Headers / Footers in HSLF + 44953 - Extensive fixes for data validation + 45519 - Fixed to keep datavalidation records together + Support for creating new HSLF CurrentUserAtoms + 45466 - Partial support for removing excel comments (won't work for all excel versions yet) 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does 45414 - Don't add too many UncalcedRecords to sheets with charts in them diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 85b57f089..175b92088 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -113,262 +113,138 @@ public final class BiffViewer { { switch ( in.getSid() ) { - case ChartRecord.sid: - return new ChartRecord( in ); - case ChartFormatRecord.sid: - return new ChartFormatRecord( in ); - case SeriesRecord.sid: - return new SeriesRecord( in ); - case BeginRecord.sid: - return new BeginRecord( in ); - case EndRecord.sid: - return new EndRecord( in ); - case BOFRecord.sid: - return new BOFRecord( in ); - case InterfaceHdrRecord.sid: - return new InterfaceHdrRecord( in ); - case MMSRecord.sid: - return new MMSRecord( in ); - case InterfaceEndRecord.sid: - return new InterfaceEndRecord( in ); - case WriteAccessRecord.sid: - return new WriteAccessRecord( in ); - case CodepageRecord.sid: - return new CodepageRecord( in ); - case DSFRecord.sid: - return new DSFRecord( in ); - case TabIdRecord.sid: - return new TabIdRecord( in ); - case FnGroupCountRecord.sid: - return new FnGroupCountRecord( in ); - case WindowProtectRecord.sid: - return new WindowProtectRecord( in ); - case ProtectRecord.sid: - return new ProtectRecord( in ); - case PasswordRecord.sid: - return new PasswordRecord( in ); - case ProtectionRev4Record.sid: - return new ProtectionRev4Record( in ); - case PasswordRev4Record.sid: - return new PasswordRev4Record( in ); - case WindowOneRecord.sid: - return new WindowOneRecord( in ); - case BackupRecord.sid: - return new BackupRecord( in ); - case HideObjRecord.sid: - return new HideObjRecord( in ); - case DateWindow1904Record.sid: - return new DateWindow1904Record( in ); - case PrecisionRecord.sid: - return new PrecisionRecord( in ); - case RefreshAllRecord.sid: - return new RefreshAllRecord( in ); - case BookBoolRecord.sid: - return new BookBoolRecord( in ); - case FontRecord.sid: - return new FontRecord( in ); - case FormatRecord.sid: - return new FormatRecord( in ); - case ExtendedFormatRecord.sid: - return new ExtendedFormatRecord( in ); - case StyleRecord.sid: - return new StyleRecord( in ); - case UseSelFSRecord.sid: - return new UseSelFSRecord( in ); - case BoundSheetRecord.sid: - return new BoundSheetRecord( in ); - case CountryRecord.sid: - return new CountryRecord( in ); - case SSTRecord.sid: - return new SSTRecord( in ); - case ExtSSTRecord.sid: - return new ExtSSTRecord( in ); - case EOFRecord.sid: - return new EOFRecord( in ); - case IndexRecord.sid: - return new IndexRecord( in ); - case CalcModeRecord.sid: - return new CalcModeRecord( in ); - case CalcCountRecord.sid: - return new CalcCountRecord( in ); - case RefModeRecord.sid: - return new RefModeRecord( in ); - case IterationRecord.sid: - return new IterationRecord( in ); - case DeltaRecord.sid: - return new DeltaRecord( in ); - case SaveRecalcRecord.sid: - return new SaveRecalcRecord( in ); - case PrintHeadersRecord.sid: - return new PrintHeadersRecord( in ); - case PrintGridlinesRecord.sid: - return new PrintGridlinesRecord( in ); - case GridsetRecord.sid: - return new GridsetRecord( in ); - case DrawingGroupRecord.sid: - return new DrawingGroupRecord( in ); - case DrawingRecordForBiffViewer.sid: - return new DrawingRecordForBiffViewer( in ); - case DrawingSelectionRecord.sid: - return new DrawingSelectionRecord( in ); - case GutsRecord.sid: - return new GutsRecord( in ); - case DefaultRowHeightRecord.sid: - return new DefaultRowHeightRecord( in ); - case WSBoolRecord.sid: - return new WSBoolRecord( in ); - case HeaderRecord.sid: - return new HeaderRecord( in ); - case FooterRecord.sid: - return new FooterRecord( in ); - case HCenterRecord.sid: - return new HCenterRecord( in ); - case VCenterRecord.sid: - return new VCenterRecord( in ); - case PrintSetupRecord.sid: - return new PrintSetupRecord( in ); - case DefaultColWidthRecord.sid: - return new DefaultColWidthRecord( in ); - case DimensionsRecord.sid: - return new DimensionsRecord( in ); - case RowRecord.sid: - return new RowRecord( in ); - case LabelSSTRecord.sid: - return new LabelSSTRecord( in ); - case RKRecord.sid: - return new RKRecord( in ); - case NumberRecord.sid: - return new NumberRecord( in ); - case DBCellRecord.sid: - return new DBCellRecord( in ); - case WindowTwoRecord.sid: - return new WindowTwoRecord( in ); - case SelectionRecord.sid: - return new SelectionRecord( in ); - case ContinueRecord.sid: - return new ContinueRecord( in ); - case LabelRecord.sid: - return new LabelRecord( in ); - case MulRKRecord.sid: - return new MulRKRecord( in ); - case MulBlankRecord.sid: - return new MulBlankRecord( in ); - case BlankRecord.sid: - return new BlankRecord( in ); - case BoolErrRecord.sid: - return new BoolErrRecord( in ); - case ColumnInfoRecord.sid: - return new ColumnInfoRecord( in ); - case MergeCellsRecord.sid: - return new MergeCellsRecord( in ); - case AreaRecord.sid: - return new AreaRecord( in ); - case DataFormatRecord.sid: - return new DataFormatRecord( in ); - case BarRecord.sid: - return new BarRecord( in ); - case DatRecord.sid: - return new DatRecord( in ); - case PlotGrowthRecord.sid: - return new PlotGrowthRecord( in ); - case UnitsRecord.sid: - return new UnitsRecord( in ); - case FrameRecord.sid: - return new FrameRecord( in ); - case ValueRangeRecord.sid: - return new ValueRangeRecord( in ); - case SeriesListRecord.sid: - return new SeriesListRecord( in ); - case FontBasisRecord.sid: - return new FontBasisRecord( in ); - case FontIndexRecord.sid: - return new FontIndexRecord( in ); - case LineFormatRecord.sid: - return new LineFormatRecord( in ); - case AreaFormatRecord.sid: - return new AreaFormatRecord( in ); - case LinkedDataRecord.sid: - return new LinkedDataRecord( in ); - case FormulaRecord.sid: - return new FormulaRecord( in ); - case SheetPropertiesRecord.sid: - return new SheetPropertiesRecord( in ); - case DefaultDataLabelTextPropertiesRecord.sid: - return new DefaultDataLabelTextPropertiesRecord( in ); - case TextRecord.sid: - return new TextRecord( in ); - case AxisParentRecord.sid: - return new AxisParentRecord( in ); - case AxisLineFormatRecord.sid: - return new AxisLineFormatRecord( in ); - case SupBookRecord.sid: - return new SupBookRecord( in ); - case ExternSheetRecord.sid: - return new ExternSheetRecord( in ); - case SCLRecord.sid: - return new SCLRecord( in ); - case SeriesToChartGroupRecord.sid: - return new SeriesToChartGroupRecord( in ); - case AxisUsedRecord.sid: - return new AxisUsedRecord( in ); - case AxisRecord.sid: - return new AxisRecord( in ); - case CategorySeriesAxisRecord.sid: - return new CategorySeriesAxisRecord( in ); - case AxisOptionsRecord.sid: - return new AxisOptionsRecord( in ); - case TickRecord.sid: - return new TickRecord( in ); - case SeriesTextRecord.sid: - return new SeriesTextRecord( in ); - case ObjectLinkRecord.sid: - return new ObjectLinkRecord( in ); - case PlotAreaRecord.sid: - return new PlotAreaRecord( in ); - case SeriesIndexRecord.sid: - return new SeriesIndexRecord( in ); - case LegendRecord.sid: - return new LegendRecord( in ); - case LeftMarginRecord.sid: - return new LeftMarginRecord( in ); - case RightMarginRecord.sid: - return new RightMarginRecord( in ); - case TopMarginRecord.sid: - return new TopMarginRecord( in ); - case BottomMarginRecord.sid: - return new BottomMarginRecord( in ); - case PaletteRecord.sid: - return new PaletteRecord( in ); - case StringRecord.sid: - return new StringRecord( in ); - case NameRecord.sid: - return new NameRecord( in ); - case PaneRecord.sid: - return new PaneRecord( in ); - case SharedFormulaRecord.sid: - return new SharedFormulaRecord( in); - case ObjRecord.sid: - return new ObjRecord( in); - case TextObjectRecord.sid: - return new TextObjectRecord( in); - case HorizontalPageBreakRecord.sid: - return new HorizontalPageBreakRecord( in); - case VerticalPageBreakRecord.sid: - return new VerticalPageBreakRecord( in); - case WriteProtectRecord.sid: - return new WriteProtectRecord( in); - case FilePassRecord.sid: - return new FilePassRecord(in); - case NoteRecord.sid: - return new NoteRecord( in ); - case FileSharingRecord.sid: - return new FileSharingRecord( in ); - case HyperlinkRecord.sid: - return new HyperlinkRecord( in ); - case TableRecord.sid: - return new TableRecord( in ); + case AreaFormatRecord.sid: return new AreaFormatRecord(in); + case AreaRecord.sid: return new AreaRecord(in); + case AxisLineFormatRecord.sid: return new AxisLineFormatRecord(in); + case AxisOptionsRecord.sid: return new AxisOptionsRecord(in); + case AxisParentRecord.sid: return new AxisParentRecord(in); + case AxisRecord.sid: return new AxisRecord(in); + case AxisUsedRecord.sid: return new AxisUsedRecord(in); + case BOFRecord.sid: return new BOFRecord(in); + case BackupRecord.sid: return new BackupRecord(in); + case BarRecord.sid: return new BarRecord(in); + case BeginRecord.sid: return new BeginRecord(in); + case BlankRecord.sid: return new BlankRecord(in); + case BookBoolRecord.sid: return new BookBoolRecord(in); + case BoolErrRecord.sid: return new BoolErrRecord(in); + case BottomMarginRecord.sid: return new BottomMarginRecord(in); + case BoundSheetRecord.sid: return new BoundSheetRecord(in); + case CalcCountRecord.sid: return new CalcCountRecord(in); + case CalcModeRecord.sid: return new CalcModeRecord(in); + case CategorySeriesAxisRecord.sid: return new CategorySeriesAxisRecord(in); + case ChartFormatRecord.sid: return new ChartFormatRecord(in); + case ChartRecord.sid: return new ChartRecord(in); + case CodepageRecord.sid: return new CodepageRecord(in); + case ColumnInfoRecord.sid: return new ColumnInfoRecord(in); + case ContinueRecord.sid: return new ContinueRecord(in); + case CountryRecord.sid: return new CountryRecord(in); + case DBCellRecord.sid: return new DBCellRecord(in); + case DSFRecord.sid: return new DSFRecord(in); + case DatRecord.sid: return new DatRecord(in); + case DataFormatRecord.sid: return new DataFormatRecord(in); + case DateWindow1904Record.sid: return new DateWindow1904Record(in); + case DefaultColWidthRecord.sid:return new DefaultColWidthRecord(in); + case DefaultDataLabelTextPropertiesRecord.sid: return new DefaultDataLabelTextPropertiesRecord(in); + case DefaultRowHeightRecord.sid: return new DefaultRowHeightRecord(in); + case DeltaRecord.sid: return new DeltaRecord(in); + case DimensionsRecord.sid: return new DimensionsRecord(in); + case DrawingGroupRecord.sid: return new DrawingGroupRecord(in); + case DrawingRecordForBiffViewer.sid: return new DrawingRecordForBiffViewer(in); + case DrawingSelectionRecord.sid: return new DrawingSelectionRecord(in); + case DVRecord.sid: return new DVRecord(in); + case DVALRecord.sid: return new DVALRecord(in); + case EOFRecord.sid: return new EOFRecord(in); + case EndRecord.sid: return new EndRecord(in); + case ExtSSTRecord.sid: return new ExtSSTRecord(in); + case ExtendedFormatRecord.sid: return new ExtendedFormatRecord(in); + case ExternSheetRecord.sid: return new ExternSheetRecord(in); + case FilePassRecord.sid: return new FilePassRecord(in); + case FileSharingRecord.sid: return new FileSharingRecord(in); + case FnGroupCountRecord.sid: return new FnGroupCountRecord(in); + case FontBasisRecord.sid: return new FontBasisRecord(in); + case FontIndexRecord.sid: return new FontIndexRecord(in); + case FontRecord.sid: return new FontRecord(in); + case FooterRecord.sid: return new FooterRecord(in); + case FormatRecord.sid: return new FormatRecord(in); + case FormulaRecord.sid: return new FormulaRecord(in); + case FrameRecord.sid: return new FrameRecord(in); + case GridsetRecord.sid: return new GridsetRecord(in); + case GutsRecord.sid: return new GutsRecord(in); + case HCenterRecord.sid: return new HCenterRecord(in); + case HeaderRecord.sid: return new HeaderRecord(in); + case HideObjRecord.sid: return new HideObjRecord(in); + case HorizontalPageBreakRecord.sid: return new HorizontalPageBreakRecord(in); + case HyperlinkRecord.sid: return new HyperlinkRecord(in); + case IndexRecord.sid: return new IndexRecord(in); + case InterfaceEndRecord.sid: return new InterfaceEndRecord(in); + case InterfaceHdrRecord.sid: return new InterfaceHdrRecord(in); + case IterationRecord.sid: return new IterationRecord(in); + case LabelRecord.sid: return new LabelRecord(in); + case LabelSSTRecord.sid: return new LabelSSTRecord(in); + case LeftMarginRecord.sid: return new LeftMarginRecord(in); + case LegendRecord.sid: return new LegendRecord(in); + case LineFormatRecord.sid: return new LineFormatRecord(in); + case LinkedDataRecord.sid: return new LinkedDataRecord(in); + case MMSRecord.sid: return new MMSRecord(in); + case MergeCellsRecord.sid: return new MergeCellsRecord(in); + case MulBlankRecord.sid: return new MulBlankRecord(in); + case MulRKRecord.sid: return new MulRKRecord(in); + case NameRecord.sid: return new NameRecord(in); + case NoteRecord.sid: return new NoteRecord(in); + case NumberRecord.sid: return new NumberRecord(in); + case ObjRecord.sid: return new ObjRecord(in); + case ObjectLinkRecord.sid: return new ObjectLinkRecord(in); + case PaletteRecord.sid: return new PaletteRecord(in); + case PaneRecord.sid: return new PaneRecord(in); + case PasswordRecord.sid: return new PasswordRecord(in); + case PasswordRev4Record.sid: return new PasswordRev4Record(in); + case PlotAreaRecord.sid: return new PlotAreaRecord(in); + case PlotGrowthRecord.sid: return new PlotGrowthRecord(in); + case PrecisionRecord.sid: return new PrecisionRecord(in); + case PrintGridlinesRecord.sid: return new PrintGridlinesRecord(in); + case PrintHeadersRecord.sid: return new PrintHeadersRecord(in); + case PrintSetupRecord.sid: return new PrintSetupRecord(in); + case ProtectRecord.sid: return new ProtectRecord(in); + case ProtectionRev4Record.sid: return new ProtectionRev4Record(in); + case RKRecord.sid: return new RKRecord(in); + case RefModeRecord.sid: return new RefModeRecord(in); + case RefreshAllRecord.sid: return new RefreshAllRecord(in); + case RightMarginRecord.sid: return new RightMarginRecord(in); + case RowRecord.sid: return new RowRecord(in); + case SCLRecord.sid: return new SCLRecord(in); + case SSTRecord.sid: return new SSTRecord(in); + case SaveRecalcRecord.sid: return new SaveRecalcRecord(in); + case SelectionRecord.sid: return new SelectionRecord(in); + case SeriesIndexRecord.sid: return new SeriesIndexRecord(in); + case SeriesListRecord.sid: return new SeriesListRecord(in); + case SeriesRecord.sid: return new SeriesRecord(in); + case SeriesTextRecord.sid: return new SeriesTextRecord(in); + case SeriesToChartGroupRecord.sid: return new SeriesToChartGroupRecord(in); + case SharedFormulaRecord.sid: return new SharedFormulaRecord(in); + case SheetPropertiesRecord.sid:return new SheetPropertiesRecord(in); + case StringRecord.sid: return new StringRecord(in); + case StyleRecord.sid: return new StyleRecord(in); + case SupBookRecord.sid: return new SupBookRecord(in); + case TabIdRecord.sid: return new TabIdRecord(in); + case TableRecord.sid: return new TableRecord(in); + case TextObjectRecord.sid: return new TextObjectRecord(in); + case TextRecord.sid: return new TextRecord(in); + case TickRecord.sid: return new TickRecord(in); + case TopMarginRecord.sid: return new TopMarginRecord(in); + case UnitsRecord.sid: return new UnitsRecord(in); + case UseSelFSRecord.sid: return new UseSelFSRecord(in); + case VCenterRecord.sid: return new VCenterRecord(in); + case ValueRangeRecord.sid: return new ValueRangeRecord(in); + case VerticalPageBreakRecord.sid: return new VerticalPageBreakRecord(in); + case WSBoolRecord.sid: return new WSBoolRecord(in); + case WindowOneRecord.sid: return new WindowOneRecord(in); + case WindowProtectRecord.sid: return new WindowProtectRecord(in); + case WindowTwoRecord.sid: return new WindowTwoRecord(in); + case WriteAccessRecord.sid: return new WriteAccessRecord(in); + case WriteProtectRecord.sid: return new WriteProtectRecord(in); + } - return new UnknownRecord( in ); + return new UnknownRecord(in); } diff --git a/src/java/org/apache/poi/hssf/dev/HSSF.java b/src/java/org/apache/poi/hssf/dev/HSSF.java index 4302357c4..5297f0781 100644 --- a/src/java/org/apache/poi/hssf/dev/HSSF.java +++ b/src/java/org/apache/poi/hssf/dev/HSSF.java @@ -18,14 +18,21 @@ package org.apache.poi.hssf.dev; -import java.io.IOException; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFDataFormat; +import org.apache.poi.hssf.usermodel.HSSFFont; +import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.util.CellRangeAddress; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.util.Region; -import org.apache.poi.hssf.usermodel.*; -import org.apache.poi.hssf.util.*; /** * File for HSSF testing/examples @@ -127,7 +134,7 @@ public class HSSF } c = r.createCell(( short ) (cellnum + 1), HSSFCell.CELL_TYPE_STRING); - c.setCellValue("TEST"); + c.setCellValue(new HSSFRichTextString("TEST")); s.setColumnWidth(( short ) (cellnum + 1), ( short ) ((50 * 8) / (( double ) 1 / 20))); if ((rownum % 2) == 0) @@ -149,10 +156,8 @@ public class HSSF // c.setCellValue(0); c.setCellStyle(cs3); } - s.addMergedRegion(new Region(( short ) 0, ( short ) 0, ( short ) 3, - ( short ) 3)); - s.addMergedRegion(new Region(( short ) 100, ( short ) 100, - ( short ) 110, ( short ) 110)); + s.addMergedRegion(new CellRangeAddress(0, 3, 0, 3)); + s.addMergedRegion(new CellRangeAddress(100, 110, 100, 110)); // end draw thick black border // create a sheet, set its title then delete it diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index afe7de239..eb3f2ad6c 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.record.formula.*; import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.AreaReference; import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference.NameType; @@ -66,8 +67,11 @@ public final class FormulaParser { public static final int FORMULA_TYPE_CELL = 0; public static final int FORMULA_TYPE_SHARED = 1; public static final int FORMULA_TYPE_ARRAY =2; - public static final int FORMULA_TYPE_CONDFOMRAT = 3; + public static final int FORMULA_TYPE_CONDFORMAT = 3; public static final int FORMULA_TYPE_NAMEDRANGE = 4; + // this constant is currently very specific. The exact differences from general data + // validation formulas or conditional format formulas is not known yet + public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5; private final String formulaString; private final int formulaLength; @@ -75,12 +79,6 @@ public final class FormulaParser { private ParseNode _rootNode; - /** - * Used for spotting if we have a cell reference, - * or a named range - */ - private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+"); - private static char TAB = '\t'; /** @@ -112,9 +110,13 @@ public final class FormulaParser { } public static Ptg[] parse(String formula, Workbook book) { - FormulaParser fp = new FormulaParser(formula, book); + return parse(formula, book, FORMULA_TYPE_CELL); + } + + public static Ptg[] parse(String formula, Workbook workbook, int formulaType) { + FormulaParser fp = new FormulaParser(formula, workbook); fp.parse(); - return fp.getRPNPtg(); + return fp.getRPNPtg(formulaType); } /** Read New Character From Input Stream */ diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java index 07d2bd2fd..9b5804f0c 100644 --- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java +++ b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java @@ -66,6 +66,9 @@ final class OperandClassTransformer { case FormulaParser.FORMULA_TYPE_CELL: rootNodeOperandClass = Ptg.CLASS_VALUE; break; + case FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST: + rootNodeOperandClass = Ptg.CLASS_REF; + break; default: throw new RuntimeException("Incomplete code - formula type (" + _formulaType + ") not supported yet"); diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java index 03177c7c2..bec1c40e6 100755 --- a/src/java/org/apache/poi/hssf/model/RecordStream.java +++ b/src/java/org/apache/poi/hssf/model/RecordStream.java @@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.Record; * * @author Josh Micich */ -final class RecordStream { +public final class RecordStream { private final List _list; private int _nextIndex; diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index cbbe663b1..1f9cfdff4 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -17,13 +17,14 @@ package org.apache.poi.hssf.model; -import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.*; // normally I don't do this, buy we literally mean ALL import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; +import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; -import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.util.POILogFactory; @@ -31,7 +32,7 @@ import org.apache.poi.util.POILogger; import java.util.ArrayList; import java.util.Iterator; -import java.util.List; // normally I don't do this, buy we literally mean ALL +import java.util.List; /** * Low level model implementation of a Sheet (one workbook contains many sheets) @@ -90,6 +91,7 @@ public final class Sheet implements Model { protected ProtectRecord protect = null; protected PageBreakRecord rowBreaks = null; protected PageBreakRecord colBreaks = null; + private DataValidityTable _dataValidityTable= null; protected ObjectProtectRecord objprotect = null; protected ScenarioProtectRecord scenprotect = null; protected PasswordRecord password = null; @@ -299,7 +301,12 @@ public final class Sheet implements Model { // and POI always re-calculates its contents rec = null; } - + else if ( rec.getSid() == DVALRecord.sid) { + RecordStream rs = new RecordStream(recs, k); + retval._dataValidityTable = new DataValidityTable(rs); + k += rs.getCountRead() - 1; // TODO - convert this method result to be zero based + rec = retval._dataValidityTable; + } else if ( rec.getSid() == ProtectRecord.sid ) { retval.protect = (ProtectRecord) rec; @@ -425,56 +432,56 @@ public final class Sheet implements Model { Sheet retval = new Sheet(); ArrayList records = new ArrayList(30); - records.add(retval.createBOF()); + records.add(createBOF()); // records.add(retval.createIndex()); - records.add(retval.createCalcMode()); - records.add(retval.createCalcCount() ); - records.add( retval.createRefMode() ); - records.add( retval.createIteration() ); - records.add( retval.createDelta() ); - records.add( retval.createSaveRecalc() ); - records.add( retval.createPrintHeaders() ); - retval.printGridlines = (PrintGridlinesRecord) retval.createPrintGridlines(); + records.add(createCalcMode()); + records.add(createCalcCount() ); + records.add(createRefMode() ); + records.add(createIteration() ); + records.add(createDelta() ); + records.add(createSaveRecalc() ); + records.add(createPrintHeaders() ); + retval.printGridlines = createPrintGridlines(); records.add( retval.printGridlines ); - retval.gridset = (GridsetRecord) retval.createGridset(); + retval.gridset = createGridset(); records.add( retval.gridset ); records.add( retval.createGuts() ); - retval.defaultrowheight = - (DefaultRowHeightRecord) retval.createDefaultRowHeight(); + retval.defaultrowheight = createDefaultRowHeight(); records.add( retval.defaultrowheight ); records.add( retval.createWSBool() ); - + + // 'Page Settings Block' retval.rowBreaks = new PageBreakRecord(PageBreakRecord.HORIZONTAL_SID); records.add(retval.rowBreaks); retval.colBreaks = new PageBreakRecord(PageBreakRecord.VERTICAL_SID); records.add(retval.colBreaks); - retval.header = (HeaderRecord) retval.createHeader(); + retval.header = createHeader(); records.add( retval.header ); - retval.footer = (FooterRecord) retval.createFooter(); + retval.footer = createFooter(); records.add( retval.footer ); - records.add( retval.createHCenter() ); - records.add( retval.createVCenter() ); - retval.printSetup = (PrintSetupRecord) retval.createPrintSetup(); + records.add(createHCenter() ); + records.add(createVCenter() ); + retval.printSetup = createPrintSetup(); records.add( retval.printSetup ); - retval.defaultcolwidth = - (DefaultColWidthRecord) retval.createDefaultColWidth(); + + // 'Worksheet Protection Block' (after 'Page Settings Block' and before DEFCOLWIDTH) + // PROTECT record normally goes here, don't add yet since the flag is initially false + + retval.defaultcolwidth = createDefaultColWidth(); records.add( retval.defaultcolwidth); ColumnInfoRecordsAggregate columns = new ColumnInfoRecordsAggregate(); records.add( columns ); retval.columns = columns; - retval.dims = ( DimensionsRecord ) retval.createDimensions(); + retval.dims = createDimensions(); records.add(retval.dims); retval.dimsloc = records.size()-1; records.add(retval.windowTwo = retval.createWindowTwo()); retval.setLoc(records.size() - 1); - retval.selection = - (SelectionRecord) retval.createSelection(); + retval.selection = createSelection(); records.add(retval.selection); - retval.protect = (ProtectRecord) retval.createProtect(); - records.add(retval.protect); - records.add(retval.createEOF()); + records.add(new EOFRecord()); retval.records = records; @@ -509,7 +516,7 @@ public final class Sheet implements Model { } } - public int addMergedRegion(int rowFrom, short colFrom, int rowTo, short colTo) { + public int addMergedRegion(int rowFrom, int colFrom, int rowTo, int colTo) { // Validate input if (rowTo < rowFrom) { throw new IllegalArgumentException("The 'to' row (" + rowTo @@ -522,7 +529,7 @@ public final class Sheet implements Model { if (merged == null || merged.getNumAreas() == 1027) { - merged = ( MergeCellsRecord ) createMergedCells(); + merged = createMergedCells(); mergedRecords.add(merged); records.add(records.size() - 1, merged); } @@ -579,7 +586,7 @@ public final class Sheet implements Model { } } - public MergeCellsRecord.MergedRegion getMergedRegionAt(int index) + public CellRangeAddress getMergedRegionAt(int index) { //safety checks if (index >= numMergedRegions || mergedRecords.size() == 0) @@ -911,124 +918,11 @@ public final class Sheet implements Model { /** * Create a row record. (does not add it to the records contained in this sheet) - * - * @param row number - * @return RowRecord created for the passed in row number - * @see org.apache.poi.hssf.record.RowRecord */ - - public RowRecord createRow(int row) - { + private static RowRecord createRow(int row) { return RowRecordsAggregate.createRow( row ); } - /** - * Create a LABELSST Record (does not add it to the records contained in this sheet) - * - * @param row the row the LabelSST is a member of - * @param col the column the LabelSST defines - * @param index the index of the string within the SST (use workbook addSSTString method) - * @return LabelSSTRecord newly created containing your SST Index, row,col. - * @see org.apache.poi.hssf.record.SSTRecord - */ - public LabelSSTRecord createLabelSST(int row, short col, int index) - { - log.logFormatted(POILogger.DEBUG, "create labelsst row,col,index %,%,%", - new int[] - { - row, col, index - }); - LabelSSTRecord rec = new LabelSSTRecord(); - - rec.setRow(row); - rec.setColumn(col); - rec.setSSTIndex(index); - rec.setXFIndex(( short ) 0x0f); - return rec; - } - - /** - * Create a NUMBER Record (does not add it to the records contained in this sheet) - * - * @param row the row the NumberRecord is a member of - * @param col the column the NumberRecord defines - * @param value for the number record - * - * @return NumberRecord for that row, col containing that value as added to the sheet - */ - public NumberRecord createNumber(int row, short col, double value) - { - log.logFormatted(POILogger.DEBUG, "create number row,col,value %,%,%", - new double[] - { - row, col, value - }); - NumberRecord rec = new NumberRecord(); - - rec.setRow(row); - rec.setColumn(col); - rec.setValue(value); - rec.setXFIndex(( short ) 0x0f); - return rec; - } - - /** - * create a BLANK record (does not add it to the records contained in this sheet) - * - * @param row - the row the BlankRecord is a member of - * @param col - the column the BlankRecord is a member of - */ - public BlankRecord createBlank(int row, short col) - { - log.logFormatted(POILogger.DEBUG, "create blank row,col %,%", new int[] - { - row, col - }); - BlankRecord rec = new BlankRecord(); - - rec.setRow(row); - rec.setColumn(col); - rec.setXFIndex(( short ) 0x0f); - return rec; - } - - /** - * Attempts to parse the formula into PTGs and create a formula record - * DOES NOT WORK YET - * - * @param row - the row for the formula record - * @param col - the column of the formula record - * @param formula - a String representing the formula. To be parsed to PTGs - * @return bogus/useless formula record - */ - public FormulaRecord createFormula(int row, short col, String formula) - { - log.logFormatted(POILogger.DEBUG, "create formula row,col,formula %,%,%", - new int[] - { - row, col - }, formula); - FormulaRecord rec = new FormulaRecord(); - - rec.setRow(row); - rec.setColumn(col); - rec.setOptions(( short ) 2); - rec.setValue(0); - rec.setXFIndex(( short ) 0x0f); - FormulaParser fp = new FormulaParser(formula,null); //fix - do we need this method? - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - int size = 0; - - for (int k = 0; k < ptg.length; k++) - { - size += ptg[ k ].getSize(); - rec.pushExpressionToken(ptg[ k ]); - } - rec.setExpressionLength(( short ) size); - return rec; - } - /** * Adds a value record to the sheet's contained binary records * (i.e. LabelSSTRecord or NumberRecord). @@ -1247,13 +1141,8 @@ public final class Sheet implements Model { /** * creates the BOF record - * @see org.apache.poi.hssf.record.BOFRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a BOFRecord */ - - protected Record createBOF() - { + private static BOFRecord createBOF() { BOFRecord retval = new BOFRecord(); retval.setVersion(( short ) 0x600); @@ -1266,31 +1155,10 @@ public final class Sheet implements Model { return retval; } - /** - * creates the Index record - not currently used - * @see org.apache.poi.hssf.record.IndexRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a IndexRecord - */ - - protected Record createIndex() - { - IndexRecord retval = new IndexRecord(); - - retval.setFirstRow(0); // must be set explicitly - retval.setLastRowAdd1(0); - return retval; - } - /** * creates the CalcMode record and sets it to 1 (automatic formula caculation) - * @see org.apache.poi.hssf.record.CalcModeRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a CalcModeRecord */ - - protected Record createCalcMode() - { + private static CalcModeRecord createCalcMode() { CalcModeRecord retval = new CalcModeRecord(); retval.setCalcMode(( short ) 1); @@ -1298,29 +1166,19 @@ public final class Sheet implements Model { } /** - * creates the CalcCount record and sets it to 0x64 (default number of iterations) - * @see org.apache.poi.hssf.record.CalcCountRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a CalcCountRecord + * creates the CalcCount record and sets it to 100 (default number of iterations) */ - - protected Record createCalcCount() - { + private static CalcCountRecord createCalcCount() { CalcCountRecord retval = new CalcCountRecord(); - retval.setIterations(( short ) 0x64); // default 64 iterations + retval.setIterations(( short ) 100); // default 100 iterations return retval; } /** * creates the RefMode record and sets it to A1 Mode (default reference mode) - * @see org.apache.poi.hssf.record.RefModeRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a RefModeRecord */ - - protected Record createRefMode() - { + private static RefModeRecord createRefMode() { RefModeRecord retval = new RefModeRecord(); retval.setMode(RefModeRecord.USE_A1_MODE); @@ -1329,13 +1187,8 @@ public final class Sheet implements Model { /** * creates the Iteration record and sets it to false (don't iteratively calculate formulas) - * @see org.apache.poi.hssf.record.IterationRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a IterationRecord */ - - protected Record createIteration() - { + private static IterationRecord createIteration() { IterationRecord retval = new IterationRecord(); retval.setIteration(false); @@ -1344,13 +1197,8 @@ public final class Sheet implements Model { /** * creates the Delta record and sets it to 0.0010 (default accuracy) - * @see org.apache.poi.hssf.record.DeltaRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a DeltaRecord */ - - protected Record createDelta() - { + private static DeltaRecord createDelta() { DeltaRecord retval = new DeltaRecord(); retval.setMaxChange(0.0010); @@ -1359,13 +1207,8 @@ public final class Sheet implements Model { /** * creates the SaveRecalc record and sets it to true (recalculate before saving) - * @see org.apache.poi.hssf.record.SaveRecalcRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a SaveRecalcRecord */ - - protected Record createSaveRecalc() - { + private static SaveRecalcRecord createSaveRecalc() { SaveRecalcRecord retval = new SaveRecalcRecord(); retval.setRecalc(true); @@ -1374,13 +1217,8 @@ public final class Sheet implements Model { /** * creates the PrintHeaders record and sets it to false (we don't create headers yet so why print them) - * @see org.apache.poi.hssf.record.PrintHeadersRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a PrintHeadersRecord */ - - protected Record createPrintHeaders() - { + private static PrintHeadersRecord createPrintHeaders() { PrintHeadersRecord retval = new PrintHeadersRecord(); retval.setPrintHeaders(false); @@ -1390,14 +1228,8 @@ public final class Sheet implements Model { /** * creates the PrintGridlines record and sets it to false (that makes for ugly sheets). As far as I can * tell this does the same thing as the GridsetRecord - * - * @see org.apache.poi.hssf.record.PrintGridlinesRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a PrintGridlinesRecord */ - - protected Record createPrintGridlines() - { + private static PrintGridlinesRecord createPrintGridlines() { PrintGridlinesRecord retval = new PrintGridlinesRecord(); retval.setPrintGridlines(false); @@ -1406,13 +1238,8 @@ public final class Sheet implements Model { /** * creates the Gridset record and sets it to true (user has mucked with the gridlines) - * @see org.apache.poi.hssf.record.GridsetRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a GridsetRecord */ - - protected Record createGridset() - { + private static GridsetRecord createGridset() { GridsetRecord retval = new GridsetRecord(); retval.setGridset(true); @@ -1421,13 +1248,8 @@ public final class Sheet implements Model { /** * creates the Guts record and sets leftrow/topcol guttter and rowlevelmax/collevelmax to 0 - * @see org.apache.poi.hssf.record.GutsRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a GutsRecordRecord - */ - - protected Record createGuts() - { + */ + private static GutsRecord createGuts() { GutsRecord retval = new GutsRecord(); retval.setLeftRowGutter(( short ) 0); @@ -1439,13 +1261,8 @@ public final class Sheet implements Model { /** * creates the DefaultRowHeight Record and sets its options to 0 and rowheight to 0xff - * @see org.apache.poi.hssf.record.DefaultRowHeightRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a DefaultRowHeightRecord */ - - protected Record createDefaultRowHeight() - { + private static DefaultRowHeightRecord createDefaultRowHeight() { DefaultRowHeightRecord retval = new DefaultRowHeightRecord(); retval.setOptionFlags(( short ) 0); @@ -1455,13 +1272,8 @@ public final class Sheet implements Model { /** * creates the WSBoolRecord and sets its values to defaults - * @see org.apache.poi.hssf.record.WSBoolRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a WSBoolRecord */ - - protected Record createWSBool() - { + private static WSBoolRecord createWSBool() { WSBoolRecord retval = new WSBoolRecord(); retval.setWSBool1(( byte ) 0x4); @@ -1471,13 +1283,8 @@ public final class Sheet implements Model { /** * creates the Header Record and sets it to nothing/0 length - * @see org.apache.poi.hssf.record.HeaderRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a HeaderRecord */ - - protected Record createHeader() - { + private static HeaderRecord createHeader() { HeaderRecord retval = new HeaderRecord(); retval.setHeaderLength(( byte ) 0); @@ -1487,13 +1294,8 @@ public final class Sheet implements Model { /** * creates the Footer Record and sets it to nothing/0 length - * @see org.apache.poi.hssf.record.FooterRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a FooterRecord */ - - protected Record createFooter() - { + private static FooterRecord createFooter() { FooterRecord retval = new FooterRecord(); retval.setFooterLength(( byte ) 0); @@ -1503,13 +1305,8 @@ public final class Sheet implements Model { /** * creates the HCenter Record and sets it to false (don't horizontally center) - * @see org.apache.poi.hssf.record.HCenterRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a HCenterRecord */ - - protected Record createHCenter() - { + private static HCenterRecord createHCenter() { HCenterRecord retval = new HCenterRecord(); retval.setHCenter(false); @@ -1518,13 +1315,8 @@ public final class Sheet implements Model { /** * creates the VCenter Record and sets it to false (don't horizontally center) - * @see org.apache.poi.hssf.record.VCenterRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a VCenterRecord - */ - - protected Record createVCenter() - { + */ + private static VCenterRecord createVCenter() { VCenterRecord retval = new VCenterRecord(); retval.setVCenter(false); @@ -1537,9 +1329,7 @@ public final class Sheet implements Model { * @see org.apache.poi.hssf.record.Record * @return record containing a PrintSetupRecord */ - - protected Record createPrintSetup() - { + private static PrintSetupRecord createPrintSetup() { PrintSetupRecord retval = new PrintSetupRecord(); retval.setPaperSize(( short ) 1); @@ -1558,30 +1348,13 @@ public final class Sheet implements Model { /** * creates the DefaultColWidth Record and sets it to 8 - * @see org.apache.poi.hssf.record.DefaultColWidthRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a DefaultColWidthRecord - */ - - protected Record createDefaultColWidth() - { + */ + private static DefaultColWidthRecord createDefaultColWidth() { DefaultColWidthRecord retval = new DefaultColWidthRecord(); - retval.setColWidth(( short ) 8); return retval; } - /** - * creates the ColumnInfo Record and sets it to a default column/width - * @see org.apache.poi.hssf.record.ColumnInfoRecord - * @return record containing a ColumnInfoRecord - */ - // TODO change return type to ColumnInfoRecord - protected Record createColInfo() - { - return ColumnInfoRecordsAggregate.createColInfo(); - } - /** * get the default column width for the sheet (if the columns do not define their own width) * @return default column width @@ -1600,7 +1373,7 @@ public final class Sheet implements Model { public boolean isGridsPrinted() { if (gridset == null) { - gridset = (GridsetRecord)createGridset(); + gridset = createGridset(); //Insert the newlycreated Gridset record at the end of the record (just before the EOF) int loc = findFirstRecordLocBySid(EOFRecord.sid); records.add(loc, gridset); @@ -1816,22 +1589,18 @@ public final class Sheet implements Model { GutsRecord guts = (GutsRecord) findFirstRecordBySid( GutsRecord.sid ); guts.setColLevelMax( (short) ( maxLevel+1 ) ); - if (maxLevel == 0) + if (maxLevel == 0) { guts.setTopColGutter( (short)0 ); - else + } else { guts.setTopColGutter( (short) ( 29 + (12 * (maxLevel-1)) ) ); + } } /** * creates the Dimensions Record and sets it to bogus values (you should set this yourself * or let the high level API do it for you) - * @see org.apache.poi.hssf.record.DimensionsRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a DimensionsRecord */ - - protected Record createDimensions() - { + private static DimensionsRecord createDimensions() { DimensionsRecord retval = new DimensionsRecord(); retval.setFirstCol(( short ) 0); @@ -1849,13 +1618,8 @@ public final class Sheet implements Model { * headercolor = 0x40

* pagebreakzoom = 0x0

* normalzoom = 0x0

- * @see org.apache.poi.hssf.record.WindowTwoRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a WindowTwoRecord */ - - protected WindowTwoRecord createWindowTwo() - { + private static WindowTwoRecord createWindowTwo() { WindowTwoRecord retval = new WindowTwoRecord(); retval.setOptions(( short ) 0x6b6); @@ -1869,21 +1633,9 @@ public final class Sheet implements Model { /** * Creates the Selection record and sets it to nothing selected - * - * @see org.apache.poi.hssf.record.SelectionRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a SelectionRecord - */ - - protected Record createSelection() - { - SelectionRecord retval = new SelectionRecord(); - - retval.setPane(( byte ) 0x3); - retval.setActiveCellCol(( short ) 0x0); - retval.setActiveCellRow(( short ) 0x0); - retval.setNumRefs(( short ) 0x0); - return retval; + */ + private static SelectionRecord createSelection() { + return new SelectionRecord(0, 0); } public short getTopRow() @@ -1903,19 +1655,15 @@ public final class Sheet implements Model { * Sets the left column to show in desktop window pane. * @param leftCol the left column to show in desktop window pane */ - public void setLeftCol(short leftCol){ - if (windowTwo!=null) - { + public void setLeftCol(short leftCol){ + if (windowTwo!=null) { windowTwo.setLeftCol(leftCol); - } } + } - public short getLeftCol() - { - return (windowTwo==null) ? (short) 0 : windowTwo.getLeftCol(); - } - - + public short getLeftCol() { + return (windowTwo==null) ? (short) 0 : windowTwo.getLeftCol(); + } /** * Returns the active row @@ -1948,18 +1696,14 @@ public final class Sheet implements Model { } /** - * Returns the active column - * * @see org.apache.poi.hssf.record.SelectionRecord - * @return row the active column index + * @return column of the active cell */ - public short getActiveCellCol() - { - if (selection == null) - { - return (short) 0; + public short getActiveCellCol() { + if (selection == null) { + return 0; } - return selection.getActiveCellCol(); + return (short)selection.getActiveCellCol(); } /** @@ -1977,23 +1721,8 @@ public final class Sheet implements Model { } } - protected Record createMergedCells() - { - MergeCellsRecord retval = new MergeCellsRecord(); - retval.setNumAreas(( short ) 0); - return retval; - } - - /** - * creates the EOF record - * @see org.apache.poi.hssf.record.EOFRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a EOFRecord - */ - - protected Record createEOF() - { - return new EOFRecord(); + private static MergeCellsRecord createMergedCells() { + return new MergeCellsRecord(); } /** @@ -2383,28 +2112,20 @@ public final class Sheet implements Model { /** * creates a Protect record with protect set to false. - * @see org.apache.poi.hssf.record.ProtectRecord - * @see org.apache.poi.hssf.record.Record - * @return a ProtectRecord */ - protected Record createProtect() - { - if (log.check( POILogger.DEBUG )) + private static ProtectRecord createProtect() { + if (log.check( POILogger.DEBUG )) { log.log(POILogger.DEBUG, "create protect record with protection disabled"); - ProtectRecord retval = new ProtectRecord(); - - retval.setProtect(false); + } + ProtectRecord retval = new ProtectRecord(); + retval.setProtect(false); // TODO - supply param to constructor return retval; } /** * creates an ObjectProtect record with protect set to false. - * @see org.apache.poi.hssf.record.ObjectProtectRecord - * @see org.apache.poi.hssf.record.Record - * @return an ObjectProtectRecord */ - protected ObjectProtectRecord createObjectProtect() - { + private static ObjectProtectRecord createObjectProtect() { if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "create protect record with protection disabled"); ObjectProtectRecord retval = new ObjectProtectRecord(); @@ -2415,12 +2136,8 @@ public final class Sheet implements Model { /** * creates a ScenarioProtect record with protect set to false. - * @see org.apache.poi.hssf.record.ScenarioProtectRecord - * @see org.apache.poi.hssf.record.Record - * @return a ScenarioProtectRecord */ - protected ScenarioProtectRecord createScenarioProtect() - { + private static ScenarioProtectRecord createScenarioProtect() { if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "create protect record with protection disabled"); ScenarioProtectRecord retval = new ScenarioProtectRecord(); @@ -2435,9 +2152,9 @@ public final class Sheet implements Model { public ProtectRecord getProtect() { if (protect == null) { - protect = (ProtectRecord)createProtect(); - //Insert the newlycreated protect record at the end of the record (just before the EOF) - int loc = findFirstRecordLocBySid(EOFRecord.sid); + protect = createProtect(); + // Insert the newly created protect record just before DefaultColWidthRecord + int loc = findFirstRecordLocBySid(DefaultColWidthRecord.sid); records.add(loc, protect); } return protect; @@ -2459,14 +2176,11 @@ public final class Sheet implements Model { /** * creates a Password record with password set to 00. - * @see org.apache.poi.hssf.record.PasswordRecord - * @see org.apache.poi.hssf.record.Record - * @return a PasswordRecord */ - protected PasswordRecord createPassword() - { - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "create password record with 00 password"); + private static PasswordRecord createPassword() { + if (log.check( POILogger.DEBUG )) { + log.log(POILogger.DEBUG, "create password record with 00 password"); + } PasswordRecord retval = new PasswordRecord(); retval.setPassword((short)00); @@ -2892,4 +2606,10 @@ public final class Sheet implements Model { rows.expandRow( row ); } } + public DataValidityTable getOrCreateDataValidityTable() { + if (_dataValidityTable == null) { + _dataValidityTable = DataValidityTable.createForSheet(records); + } + return _dataValidityTable; + } } diff --git a/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java b/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java index 6447546ec..862adc9c5 100644 --- a/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java +++ b/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java @@ -17,35 +17,33 @@ package org.apache.poi.hssf.record; -import org.apache.poi.hssf.record.cf.CellRange; -import org.apache.poi.hssf.util.Region; +import org.apache.poi.hssf.record.cf.CellRangeUtil; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.util.LittleEndian; /** - * Conditional Formatting Header record (CFHEADER) + * Conditional Formatting Header record CFHEADER (0x1B0) * * @author Dmitriy Kumshayev */ -public final class CFHeaderRecord extends Record -{ +public final class CFHeaderRecord extends Record { public static final short sid = 0x1B0; - private static final CellRange[] EMPTY_CELL_RANGE_ARRAY = { }; - private int field_1_numcf; private int field_2_need_recalculation; - private CellRange field_3_enclosing_cell_range; - private CellRange[] field_4_cell_ranges; + private CellRangeAddress field_3_enclosing_cell_range; + private CellRangeAddressList field_4_cell_ranges; /** Creates new CFHeaderRecord */ public CFHeaderRecord() { - field_4_cell_ranges = EMPTY_CELL_RANGE_ARRAY; + field_4_cell_ranges = new CellRangeAddressList(); } - public CFHeaderRecord(org.apache.poi.ss.util.Region[] regions) + public CFHeaderRecord(CellRangeAddress[] regions) { - CellRange[] unmergedRanges = CellRange.convertRegionsToCellRanges(regions); - CellRange[] mergeCellRanges = CellRange.mergeCellRanges(unmergedRanges); + CellRangeAddress[] unmergedRanges = regions; + CellRangeAddress[] mergeCellRanges = CellRangeUtil.mergeCellRanges(unmergedRanges); setCellRanges(mergeCellRanges); } @@ -58,14 +56,8 @@ public final class CFHeaderRecord extends Record { field_1_numcf = in.readShort(); field_2_need_recalculation = in.readShort(); - field_3_enclosing_cell_range = new CellRange(in.readUShort(), in.readUShort(), in.readUShort(), in.readUShort()); - int numCellRanges = in.readShort(); - CellRange[] crs = new CellRange[numCellRanges]; - for( int i=0; i0) + buffer.append(" .cfranges=["); + for( int i=0; i @@ -34,563 +32,312 @@ import org.apache.poi.util.StringUtil; * are stored in a sequential list of DV records. This list is followed by * DVAL record(s) * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) - * @version 2.0-pre + * @author Josh Micich */ -public final class DVRecord extends Record -{ - public final static short sid = 0x01BE; - - /** - * Option flags - */ - private int field_option_flags; - - /** - * Title of the prompt box - */ - private String field_title_prompt; - - /** - * Title of the error box - */ - private String field_title_error; - - /** - * Text of the prompt box - */ - private String field_text_prompt; - - /** - * Text of the error box - */ - private String field_text_error; - - /** - * Size of the formula data for first condition - */ - private short field_size_first_formula; - - /** - * Not used - */ - private short field_not_used_1 = 0x3FE0; - - /** - * Formula data for first condition (RPN token array without size field) - */ - private Stack field_rpn_token_1 ; - - /** - * Size of the formula data for second condition - */ - private short field_size_sec_formula; - - /** - * Not used - */ - private short field_not_used_2 = 0x0000; - - /** - * Formula data for second condition (RPN token array without size field) - */ - private Stack field_rpn_token_2 ; - - /** - * Cell range address list with all affected ranges - */ - private HSSFCellRangeAddress field_regions; - - public static final Integer STRING_PROMPT_TITLE = new Integer(0); - public static final Integer STRING_ERROR_TITLE = new Integer(1); - public static final Integer STRING_PROMPT_TEXT = new Integer(2); - public static final Integer STRING_ERROR_TEXT = new Integer(3); - private Hashtable _hash_strings ; - - /** - * Option flags field - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - private BitField opt_data_type = new BitField(0x0000000F); - private BitField opt_error_style = new BitField(0x00000070); - private BitField opt_string_list_formula = new BitField(0x00000080); - private BitField opt_empty_cell_allowed = new BitField(0x00000100); - private BitField opt_surppres_dropdown_arrow = new BitField(0x00000200); - private BitField opt_show_prompt_on_cell_selected = new BitField(0x00040000); - private BitField opt_show_error_on_invalid_value = new BitField(0x00080000); - private BitField opt_condition_operator = new BitField(0x00F00000); - - public DVRecord() - { - } - - /** - * Constructs a DV record and sets its fields appropriately. - * - * @param in the RecordInputstream to read the record from - */ - - public DVRecord(RecordInputStream in) - { - super(in); - } - - protected void validateSid(short id) - { - if (id != sid) - { - throw new RecordFormatException("NOT a valid DV RECORD"); - } - } - - protected void fillFields(RecordInputStream in) - { - field_rpn_token_1 = new Stack(); - field_rpn_token_2 = new Stack(); - - this.field_option_flags = in.readInt(); - this._hash_strings = new Hashtable(4); - - StringHandler strHandler_prompt_title = new StringHandler( in ); - this.field_title_prompt = strHandler_prompt_title.getStringData(); - this._hash_strings.put(DVRecord.STRING_PROMPT_TITLE, strHandler_prompt_title); - - StringHandler strHandler_error_title = new StringHandler( in ); - this.field_title_error = strHandler_error_title.getStringData(); - this._hash_strings.put(DVRecord.STRING_ERROR_TITLE, strHandler_error_title); - - StringHandler strHandler_prompt_text = new StringHandler( in ); - this.field_text_prompt = strHandler_prompt_text.getStringData(); - this._hash_strings.put(DVRecord.STRING_PROMPT_TEXT, strHandler_prompt_text); - - StringHandler strHandler_error_text = new StringHandler( in ); - this.field_text_error = strHandler_error_text.getStringData(); - this._hash_strings.put(DVRecord.STRING_ERROR_TEXT, strHandler_error_text); - - this.field_size_first_formula = in.readShort(); - this.field_not_used_1 = in.readShort(); - - //read first formula data condition - int token_pos = 0; - while (token_pos < this.field_size_first_formula) - { - Ptg ptg = Ptg.createPtg(in); - token_pos += ptg.getSize(); - field_rpn_token_1.push(ptg); - } - - this.field_size_sec_formula = in.readShort(); - this.field_not_used_2 = in.readShort(); - - //read sec formula data condition - if (false) { // TODO - prior to bug 44710 this 'skip' was being executed. write a junit to confirm this fix - try { - in.skip(this.field_size_sec_formula); - } catch(IOException e) { - e.printStackTrace(); - throw new IllegalStateException(e.getMessage()); - } - } - token_pos = 0; - while (token_pos < this.field_size_sec_formula) - { - Ptg ptg = Ptg.createPtg(in); - token_pos += ptg.getSize(); - field_rpn_token_2.push(ptg); - } - - //read cell range address list with all affected ranges - this.field_regions = new HSSFCellRangeAddress(in); - } - - - // --> start option flags - /** - * set the condition data type - * @param type - condition data type - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setDataType(int type) - { - this.field_option_flags = this.opt_data_type.setValue(this.field_option_flags, type); - } - - /** - * get the condition data type - * @return the condition data type - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public int getDataType() - { - return this.opt_data_type.getValue(this.field_option_flags); - } - - /** - * set the condition error style - * @param type - condition error style - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setErrorStyle(int style) - { - this.field_option_flags = this.opt_error_style.setValue(this.field_option_flags, style); - } - - /** - * get the condition error style - * @return the condition error style - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public int getErrorStyle() - { - return this.opt_error_style.getValue(this.field_option_flags); - } - - /** - * set if in list validations the string list is explicitly given in the formula - * @param type - true if in list validations the string list is explicitly given in the formula; false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setListExplicitFormula(boolean explicit) - { - this.field_option_flags = this.opt_string_list_formula.setBoolean(this.field_option_flags, explicit); - } - - /** - * return true if in list validations the string list is explicitly given in the formula, false otherwise - * @return true if in list validations the string list is explicitly given in the formula, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public boolean getListExplicitFormula() - { - return (this.opt_string_list_formula.isSet(this.field_option_flags)); - } - - /** - * set if empty values are allowed in cells - * @param type - true if empty values are allowed in cells, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setEmptyCellAllowed(boolean allowed) - { - this.field_option_flags = this.opt_empty_cell_allowed.setBoolean(this.field_option_flags, allowed); - } - - /** - * return true if empty values are allowed in cells, false otherwise - * @return if empty values are allowed in cells, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public boolean getEmptyCellAllowed() - { - return (this.opt_empty_cell_allowed.isSet(this.field_option_flags)); - } - - /** - * set if drop down arrow should be surppressed when list validation is used - * @param type - true if drop down arrow should be surppressed when list validation is used, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setSurppresDropdownArrow(boolean surppress) - { - this.field_option_flags = this.opt_surppres_dropdown_arrow.setBoolean(this.field_option_flags, surppress); - } - - /** - * return true if drop down arrow should be surppressed when list validation is used, false otherwise - * @return if drop down arrow should be surppressed when list validation is used, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public boolean getSurppresDropdownArrow() - { - return (this.opt_surppres_dropdown_arrow.isSet(this.field_option_flags)); - } - - /** - * set if a prompt window should appear when cell is selected - * @param type - true if a prompt window should appear when cell is selected, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setShowPromptOnCellSelected(boolean show) - { - this.field_option_flags = this.opt_show_prompt_on_cell_selected.setBoolean(this.field_option_flags, show); - } - - /** - * return true if a prompt window should appear when cell is selected, false otherwise - * @return if a prompt window should appear when cell is selected, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public boolean getShowPromptOnCellSelected() - { - return (this.opt_show_prompt_on_cell_selected.isSet(this.field_option_flags)); - } - - /** - * set if an error window should appear when an invalid value is entered in the cell - * @param type - true if an error window should appear when an invalid value is entered in the cell, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setShowErrorOnInvalidValue(boolean show) - { - this.field_option_flags = this.opt_show_error_on_invalid_value.setBoolean(this.field_option_flags, show); - } - - /** - * return true if an error window should appear when an invalid value is entered in the cell, false otherwise - * @return if an error window should appear when an invalid value is entered in the cell, false otherwise - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public boolean getShowErrorOnInvalidValue() - { - return (this.opt_show_error_on_invalid_value.isSet(this.field_option_flags)); - } - - /** - * set the condition operator - * @param type - condition operator - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public void setConditionOperator(int operator) - { - this.field_option_flags = this.opt_condition_operator.setValue(this.field_option_flags, operator); - } - - /** - * get the condition operator - * @return the condition operator - * @see org.apache.poi.hssf.util.HSSFDataValidation utility class - */ - public int getConditionOperator() - { - return this.opt_condition_operator.getValue(this.field_option_flags); - } - // <-- end option flags - - public void setFirstFormulaRPN( Stack rpn ) - { - this.field_rpn_token_1 = rpn; - } - - public void setFirstFormulaSize( short size ) - { - this.field_size_first_formula = size; - } - - public void setSecFormulaRPN( Stack rpn ) - { - this.field_rpn_token_2 = rpn; - } - - public void setSecFormulaSize( short size ) - { - this.field_size_sec_formula = size; - } - - public void setStringField( Integer type, String str_data ) - { - if ( this._hash_strings == null ) - { - this._hash_strings = new Hashtable(); - } - StringHandler strHandler = new StringHandler(); - if ( str_data == null ) - { - str_data = ""; - } - else - { - strHandler.setStringLength(str_data.length()); - } - strHandler.setStringData(str_data); - - strHandler.setUnicodeFlag((byte)0x00); - this._hash_strings.put( type, strHandler); - } - - public String getStringField( Integer type ) - { - return ((StringHandler)this._hash_strings.get(type)).getStringData(); - } - - public void setCellRangeAddress( HSSFCellRangeAddress range ) - { - this.field_regions = range; - } - - public HSSFCellRangeAddress getCellRangeAddress( ) - { - return this.field_regions; - } - - /** - * gets the option flags field. - * @return options - the option flags field - */ - public int getOptionFlags() - { - return this.field_option_flags; - } - - public String toString() - { - /** @todo DVRecord string representation */ - StringBuffer buffer = new StringBuffer(); - - return buffer.toString(); - } - - public int serialize(int offset, byte [] data) - { - int size = this.getRecordSize(); - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) (size-4)); - - int pos = 4; - LittleEndian.putInt(data, pos + offset, this.getOptionFlags()); - pos += 4; - pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_PROMPT_TITLE )).serialize(pos+offset, data); - pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_ERROR_TITLE )).serialize(pos+offset, data); - pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_PROMPT_TEXT )).serialize(pos+offset, data); - pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_ERROR_TEXT )).serialize(pos+offset, data); - LittleEndian.putShort(data, offset+pos, this.field_size_first_formula); - pos += 2; - LittleEndian.putShort(data, offset+pos, this.field_not_used_1); - pos += 2; - - for (int k = 0; k < this.field_rpn_token_1.size(); k++) - { - Ptg ptg = ( Ptg ) this.field_rpn_token_1.get(k); - ptg.writeBytes(data, pos+offset); - pos += ptg.getSize(); - } - - LittleEndian.putShort(data, offset+pos, this.field_size_sec_formula); - pos += 2; - LittleEndian.putShort(data, offset+pos, this.field_not_used_2); - pos += 2; - if ( this.field_size_sec_formula > 0 ) - { - for (int k = 0; k < this.field_rpn_token_2.size(); k++) - { - Ptg ptg = ( Ptg ) this.field_rpn_token_2.get(k); - ptg.writeBytes(data, pos+offset); - pos += ptg.getSize(); - } - } - this.field_regions.serialize(pos+offset, data); - return size; - } - - public int getRecordSize() - { - int size = 4+4+2+2+2+2;//header+options_field+first_formula_size+first_unused+sec_formula_size+sec+unused; - if ( this._hash_strings != null ) - { - Enumeration enum_keys = this._hash_strings.keys(); - while ( enum_keys.hasMoreElements() ) - { - size += ((StringHandler)this._hash_strings.get( (Integer)enum_keys.nextElement() )).getSize(); - } - } - size += this.field_size_first_formula+ this.field_size_sec_formula; - size += this.field_regions.getSize(); - return size; - } - - public short getSid() - { - return this.sid; - } - - /** - * Clones the object. Uses serialisation, as the - * contents are somewhat complex - */ - public Object clone() { - return cloneViaReserialise(); - } - - /**@todo DVRecord = Serializare */ - - private static final class StringHandler - { - private int _string_length = 0x0001; - private byte _string_unicode_flag = 0x00; - private String _string_data = "0x00"; - private int _start_offset; - private int _end_offset; - - StringHandler() - { - - } - - StringHandler(RecordInputStream in) - { - this.fillFields(in); - } - - protected void fillFields(RecordInputStream in) - { - this._string_length = in.readUShort(); - this._string_unicode_flag = in.readByte(); - if (this._string_unicode_flag == 1) - { - this._string_data = in.readUnicodeLEString(this._string_length); - } - else - { - this._string_data = in.readCompressedUnicode(this._string_length); - } - } - - private void setStringData( String string_data ) - { - this._string_data = string_data; - } - - private String getStringData() - { - return this._string_data; - } - - private int getEndOffset() - { - return this._end_offset; - } - - public int serialize( int offset, byte[] data ) - { - LittleEndian.putUShort(data, offset, this._string_length ); - data[2 + offset] = this._string_unicode_flag; - if (this._string_unicode_flag == 1) - { - StringUtil.putUnicodeLE(this._string_data, data, 3 + offset); - } - else - { - StringUtil.putCompressedUnicode(this._string_data, data, 3 + offset); - } - return getSize(); - } - - private void setUnicodeFlag( byte flag ) - { - this._string_unicode_flag = flag; - } - - private void setStringLength( int len ) - { - this._string_length = len; - } - - private int getStringByteLength() - { - return (this._string_unicode_flag == 1) ? this._string_length * 2 : this._string_length; - } - - public int getSize() - { - return 2 + 1 + getStringByteLength(); - } - } +public final class DVRecord extends Record { + public final static short sid = 0x01BE; + + /** the unicode string used for error/prompt title/text when not present */ + private static final UnicodeString NULL_TEXT_STRING = new UnicodeString("\0"); + + /** Option flags */ + private int _option_flags; + /** Title of the prompt box */ + private UnicodeString _promptTitle; + /** Title of the error box */ + private UnicodeString _errorTitle; + /** Text of the prompt box */ + private UnicodeString _promptText; + /** Text of the error box */ + private UnicodeString _errorText; + /** Not used - Excel seems to always write 0x3FE0 */ + private short _not_used_1 = 0x3FE0; + /** Formula data for first condition (RPN token array without size field) */ + private Ptg[] _formula1; + /** Not used - Excel seems to always write 0x0000 */ + private short _not_used_2 = 0x0000; + /** Formula data for second condition (RPN token array without size field) */ + private Ptg[] _formula2; + /** Cell range address list with all affected ranges */ + private CellRangeAddressList _regions; + + /** + * Option flags field + * + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + private static final BitField opt_data_type = new BitField(0x0000000F); + private static final BitField opt_error_style = new BitField(0x00000070); + private static final BitField opt_string_list_formula = new BitField(0x00000080); + private static final BitField opt_empty_cell_allowed = new BitField(0x00000100); + private static final BitField opt_suppress_dropdown_arrow = new BitField(0x00000200); + private static final BitField opt_show_prompt_on_cell_selected = new BitField(0x00040000); + private static final BitField opt_show_error_on_invalid_value = new BitField(0x00080000); + private static final BitField opt_condition_operator = new BitField(0x00700000); + + /** + * Constructs a DV record and sets its fields appropriately. + * + * @param in the RecordInputstream to read the record from + */ + public DVRecord(RecordInputStream in) { + super(in); + } + + public DVRecord(int validationType, int operator, int errorStyle, boolean emptyCellAllowed, + boolean suppressDropDownArrow, boolean isExplicitList, + boolean showPromptBox, String promptTitle, String promptText, + boolean showErrorBox, String errorTitle, String errorText, + Ptg[] formula1, Ptg[] formula2, + CellRangeAddressList regions) { + + int flags = 0; + flags = opt_data_type.setValue(flags, validationType); + flags = opt_condition_operator.setValue(flags, operator); + flags = opt_error_style.setValue(flags, errorStyle); + flags = opt_empty_cell_allowed.setBoolean(flags, emptyCellAllowed); + flags = opt_suppress_dropdown_arrow.setBoolean(flags, suppressDropDownArrow); + flags = opt_string_list_formula.setBoolean(flags, isExplicitList); + flags = opt_show_prompt_on_cell_selected.setBoolean(flags, showPromptBox); + flags = opt_show_error_on_invalid_value.setBoolean(flags, showErrorBox); + _option_flags = flags; + _promptTitle = resolveTitleText(promptTitle); + _promptText = resolveTitleText(promptText); + _errorTitle = resolveTitleText(errorTitle); + _errorText = resolveTitleText(errorText); + _formula1 = formula1; + _formula2 = formula2; + _regions = regions; + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT a valid DV RECORD"); + } + } + + protected void fillFields(RecordInputStream in) { + + _option_flags = in.readInt(); + + _promptTitle = readUnicodeString(in); + _errorTitle = readUnicodeString(in); + _promptText = readUnicodeString(in); + _errorText = readUnicodeString(in); + + int field_size_first_formula = in.readUShort(); + _not_used_1 = in.readShort(); + + //read first formula data condition + _formula1 = Ptg.readTokens(field_size_first_formula, in); + + int field_size_sec_formula = in.readUShort(); + _not_used_2 = in.readShort(); + + //read sec formula data condition + _formula2 = Ptg.readTokens(field_size_sec_formula, in); + + //read cell range address list with all affected ranges + _regions = new org.apache.poi.hssf.util.CellRangeAddressList(in); + } + + // --> start option flags + /** + * @return the condition data type + * @see DVConstraint.ValidationType + */ + public int getDataType() { + return opt_data_type.getValue(_option_flags); + } + + /** + * @return the condition error style + * @see HSSFDataValidation.ErrorStyle + */ + public int getErrorStyle() { + return opt_error_style.getValue(_option_flags); + } + + /** + * @return true if in list validations the string list is explicitly given in the + * formula, false otherwise + */ + public boolean getListExplicitFormula() { + return (opt_string_list_formula.isSet(_option_flags)); + } + + /** + * @return true if empty values are allowed in cells, false otherwise + */ + public boolean getEmptyCellAllowed() { + return (opt_empty_cell_allowed.isSet(_option_flags)); + } + + + /** + * @return true if drop down arrow should be suppressed when list validation is + * used, false otherwise + */ + public boolean getSuppressDropdownArrow() { + return (opt_suppress_dropdown_arrow.isSet(_option_flags)); + } + + /** + * @return true if a prompt window should appear when cell is selected, false otherwise + */ + public boolean getShowPromptOnCellSelected() { + return (opt_show_prompt_on_cell_selected.isSet(_option_flags)); + } + + /** + * @return true if an error window should appear when an invalid value is entered + * in the cell, false otherwise + */ + public boolean getShowErrorOnInvalidValue() { + return (opt_show_error_on_invalid_value.isSet(_option_flags)); + } + + /** + * get the condition operator + * @return the condition operator + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public int getConditionOperator() { + return opt_condition_operator.getValue(_option_flags); + } + // <-- end option flags + + + + + public CellRangeAddressList getCellRangeAddress() { + return this._regions; + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[DV]\n"); + sb.append(" options=").append(Integer.toHexString(_option_flags)); + sb.append(" title-prompt=").append(formatTextTitle(_promptTitle)); + sb.append(" title-error=").append(formatTextTitle(_errorTitle)); + sb.append(" text-prompt=").append(formatTextTitle(_promptText)); + sb.append(" text-error=").append(formatTextTitle(_errorText)); + sb.append("\n"); + appendFormula(sb, "Formula 1:", _formula1); + appendFormula(sb, "Formula 2:", _formula2); + sb.append("Regions: "); + int nRegions = _regions.countRanges(); + for(int i=0; i0) { + sb.append(", "); + } + CellRangeAddress addr = _regions.getCellRangeAddress(i); + sb.append('(').append(addr.getFirstRow()).append(',').append(addr.getLastRow()); + sb.append(',').append(addr.getFirstColumn()).append(',').append(addr.getLastColumn()).append(')'); + } + sb.append("\n"); + sb.append("[/DV]"); + + return sb.toString(); + } + + private static String formatTextTitle(UnicodeString us) { + String str = us.getString(); + if (str.length() == 1 && str.charAt(0) == '\0') { + return "'\\0'"; + } + return str; + } + + private void appendFormula(StringBuffer sb, String label, Ptg[] ptgs) { + sb.append(label); + if (ptgs.length < 1) { + sb.append("\n"); + return; + } + sb.append("\n"); + for (int i = 0; i < ptgs.length; i++) { + sb.append('\t').append(ptgs[i].toString()).append('\n'); + } + } + + public int serialize(int offset, byte [] data) { + int size = this.getRecordSize(); + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, ( short ) (size-4)); + + int pos = 4; + LittleEndian.putInt(data, pos + offset, _option_flags); + pos += 4; + + pos += serializeUnicodeString(_promptTitle, pos+offset, data); + pos += serializeUnicodeString(_errorTitle, pos+offset, data); + pos += serializeUnicodeString(_promptText, pos+offset, data); + pos += serializeUnicodeString(_errorText, pos+offset, data); + LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula1)); + pos += 2; + LittleEndian.putUShort(data, offset+pos, _not_used_1); + pos += 2; + + pos += Ptg.serializePtgs(_formula1, data, pos+offset); + + LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula2)); + pos += 2; + LittleEndian.putShort(data, offset+pos, _not_used_2); + pos += 2; + pos += Ptg.serializePtgs(_formula2, data, pos+offset); + _regions.serialize(pos+offset, data); + return size; + } + + /** + * When entered via the UI, Excel translates empty string into "\0" + * While it is possible to encode the title/text as empty string (Excel doesn't exactly crash), + * the resulting tool-tip text / message box looks wrong. It is best to do the same as the + * Excel UI and encode 'not present' as "\0". + */ + private static UnicodeString resolveTitleText(String str) { + if (str == null || str.length() < 1) { + return NULL_TEXT_STRING; + } + return new UnicodeString(str); + } + + private static UnicodeString readUnicodeString(RecordInputStream in) { + return new UnicodeString(in); + } + + private static int serializeUnicodeString(UnicodeString us, int offset, byte[] data) { + UnicodeRecordStats urs = new UnicodeRecordStats(); + us.serialize(urs, offset, data); + return urs.recordSize; + } + private static int getUnicodeStringSize(UnicodeString str) { + return 3 + str.getString().length(); + } + + public int getRecordSize() { + int size = 4+4+2+2+2+2;//header+options_field+first_formula_size+first_unused+sec_formula_size+sec+unused; + size += getUnicodeStringSize(_promptTitle); + size += getUnicodeStringSize(_errorTitle); + size += getUnicodeStringSize(_promptText); + size += getUnicodeStringSize(_errorText); + size += Ptg.getEncodedSize(_formula1); + size += Ptg.getEncodedSize(_formula2); + size += _regions.getSize(); + return size; + } + + public short getSid() { + return sid; + } + + /** + * Clones the object. Uses serialisation, as the + * contents are somewhat complex + */ + public Object clone() { + return cloneViaReserialise(); + } } diff --git a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java index 41d8170df..2e0a417e0 100644 --- a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java +++ b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,57 +14,43 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; -import java.util.ArrayList; -import java.util.Iterator; - +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.util.LittleEndian; /** - * Title: Merged Cells Record - *
+ * Title: Merged Cells Record (0x00E5) + *
* Description: Optional record defining a square area of cells to "merged" into * one cell.
* REFERENCE: NONE (UNDOCUMENTED PRESENTLY)
* @author Andrew C. Oliver (acoliver at apache dot org) * @version 2.0-pre */ -public class MergeCellsRecord - extends Record -{ - public final static short sid = 0xe5; - private ArrayList field_2_regions; +public final class MergeCellsRecord extends Record { + public final static short sid = 0x00E5; + private CellRangeAddressList _regions; - public MergeCellsRecord() - { + /** + * Creates an empty MergedCellsRecord + */ + public MergeCellsRecord() { + _regions = new CellRangeAddressList(); } /** * Constructs a MergedCellsRecord and sets its fields appropriately * @param in the RecordInputstream to read the record from */ - - public MergeCellsRecord(RecordInputStream in) - { + public MergeCellsRecord(RecordInputStream in) { super(in); } - protected void fillFields(RecordInputStream in) - { - short numAreas = in.readShort(); - field_2_regions = new ArrayList(numAreas + 10); - - for (int k = 0; k < numAreas; k++) - { - MergedRegion region = - new MergedRegion(in.readShort(), in.readShort(), - in.readShort(), in.readShort()); - - field_2_regions.add(region); - } + protected void fillFields(RecordInputStream in) { + _regions = new org.apache.poi.hssf.util.CellRangeAddressList(in); } /** @@ -73,27 +58,8 @@ public class MergeCellsRecord * ahead and delete the record. * @return number of areas */ - - public short getNumAreas() - { - //if the array size is larger than a short (65536), the record can't hold that many merges anyway - if (field_2_regions == null) return 0; - return (short)field_2_regions.size(); - } - - /** - * set the number of merged areas. You do not need to call this if you use addArea, - * it will be incremented automatically or decremented when an area is removed. If - * you are setting this to 0 then you are a terrible person. Just remove the record. - * (just kidding about you being a terrible person..hehe) - * @deprecated We now link the size to the actual array of merged regions - * @see #getNumAreas() - * @param numareas number of areas - */ - - public void setNumAreas(short numareas) - { - + public short getNumAreas() { + return (short)_regions.countRanges(); } /** @@ -101,178 +67,84 @@ public class MergeCellsRecord * be correct provided you do not add ahead of or remove ahead of it (in which case * you should increment or decrement appropriately....in other words its an arrayList) * - * @param rowfrom - the upper left hand corner's row - * @param colfrom - the upper left hand corner's col - * @param rowto - the lower right hand corner's row - * @param colto - the lower right hand corner's col + * @param firstRow - the upper left hand corner's row + * @param firstCol - the upper left hand corner's col + * @param lastRow - the lower right hand corner's row + * @param lastCol - the lower right hand corner's col * @return new index of said area (don't depend on it if you add/remove) */ - - //public int addArea(short rowfrom, short colfrom, short rowto, short colto) - public int addArea(int rowfrom, short colfrom, int rowto, short colto) - { - if (field_2_regions == null) - { - field_2_regions = new ArrayList(10); - } - MergedRegion region = new MergedRegion(rowfrom, rowto, colfrom, - colto); - - field_2_regions.add(region); - return field_2_regions.size() - 1; + public void addArea(int firstRow, int firstCol, int lastRow, int lastCol) { + _regions.addCellRangeAddress(firstRow, firstCol, lastRow, lastCol); } /** * essentially unmerge the cells in the "area" stored at the passed in index - * @param area index + * @param areaIndex */ - - public void removeAreaAt(int area) - { - field_2_regions.remove(area); + public void removeAreaAt(int areaIndex) { + _regions.remove(areaIndex); } /** - * return the MergedRegion at the given index. - * - * @return MergedRegion representing the area that is Merged (r1,c1 - r2,c2) + * @return MergedRegion at the given index representing the area that is Merged (r1,c1 - r2,c2) */ - - public MergedRegion getAreaAt(int index) - { - return ( MergedRegion ) field_2_regions.get(index); + public CellRangeAddress getAreaAt(int index) { + return _regions.getCellRangeAddress(index); } - public int getRecordSize() - { - int retValue; - - retValue = 6 + (8 * field_2_regions.size()); - return retValue; + public int getRecordSize() { + return 4 + _regions.getSize(); } - public short getSid() - { + public short getSid() { return sid; } - public int serialize(int offset, byte [] data) - { - int recordsize = getRecordSize(); - int pos = 6; + public int serialize(int offset, byte [] data) { + int dataSize = _regions.getSize(); LittleEndian.putShort(data, offset + 0, sid); - LittleEndian.putShort(data, offset + 2, ( short ) (recordsize - 4)); - LittleEndian.putShort(data, offset + 4, getNumAreas()); - for (int k = 0; k < getNumAreas(); k++) - { - MergedRegion region = getAreaAt(k); - - //LittleEndian.putShort(data, offset + pos, region.row_from); - LittleEndian.putShort(data, offset + pos, ( short ) region.row_from); - pos += 2; - //LittleEndian.putShort(data, offset + pos, region.row_to); - LittleEndian.putShort(data, offset + pos, ( short ) region.row_to); - pos += 2; - LittleEndian.putShort(data, offset + pos, region.col_from); - pos += 2; - LittleEndian.putShort(data, offset + pos, region.col_to); - pos += 2; - } - return recordsize; + LittleEndian.putUShort(data, offset + 2, dataSize); + _regions.serialize(offset + 4, data); + return 4 + dataSize; } - public String toString() - { + public String toString() { StringBuffer retval = new StringBuffer(); retval.append("[MERGEDCELLS]").append("\n"); retval.append(" .sid =").append(sid).append("\n"); retval.append(" .numregions =").append(getNumAreas()) .append("\n"); - for (int k = 0; k < getNumAreas(); k++) - { - MergedRegion region = ( MergedRegion ) field_2_regions.get(k); + for (int k = 0; k < _regions.countRanges(); k++) { + CellRangeAddress region = _regions.getCellRangeAddress(k); - retval.append(" .rowfrom =").append(region.row_from) + retval.append(" .rowfrom =").append(region.getFirstRow()) .append("\n"); - retval.append(" .colfrom =").append(region.col_from) + retval.append(" .colfrom =").append(region.getFirstColumn()) .append("\n"); - retval.append(" .rowto =").append(region.row_to) + retval.append(" .rowto =").append(region.getLastRow()) .append("\n"); - retval.append(" .colto =").append(region.col_to) + retval.append(" .colto =").append(region.getLastColumn()) .append("\n"); } retval.append("[MERGEDCELLS]").append("\n"); return retval.toString(); } - protected void validateSid(short id) - { - if (id != sid) - { + protected void validateSid(short id) { + if (id != sid) { throw new RecordFormatException("NOT A MERGEDCELLS RECORD!! " + id); } } - /** - * this is a low level representation of a MergedRegion of cells. It is an - * inner class because we do not want it used without reference to this class. - * - */ - - public class MergedRegion - { - - /** - * create a merged region all in one stroke. - */ - - //public MergedRegion(short row_from, short row_to, short col_from, - public MergedRegion(int row_from, int row_to, short col_from, - short col_to) - { - this.row_from = row_from; - this.row_to = row_to; - this.col_from = col_from; - this.col_to = col_to; - } - - /** - * upper lefthand corner row - */ - - //public short row_from; - public int row_from; - - /** - * lower right hand corner row - */ - - //public short row_to; - public int row_to; - - /** - * upper right hand corner col - */ - - public short col_from; - - /** - * lower right hand corner col - */ - - public short col_to; - } - public Object clone() { MergeCellsRecord rec = new MergeCellsRecord(); - rec.field_2_regions = new ArrayList(); - Iterator iterator = field_2_regions.iterator(); - while (iterator.hasNext()) { - MergedRegion oldRegion = (MergedRegion)iterator.next(); - rec.addArea(oldRegion.row_from, oldRegion.col_from, oldRegion.row_to, oldRegion.col_to); + for (int k = 0; k < _regions.countRanges(); k++) { + CellRangeAddress oldRegion = _regions.getCellRangeAddress(k); + rec.addArea(oldRegion.getFirstRow(), oldRegion.getFirstColumn(), + oldRegion.getLastRow(), oldRegion.getLastColumn()); } return rec; diff --git a/src/java/org/apache/poi/hssf/record/SelectionRecord.java b/src/java/org/apache/poi/hssf/record/SelectionRecord.java index 8ad4d0cef..2f36ea644 100644 --- a/src/java/org/apache/poi/hssf/record/SelectionRecord.java +++ b/src/java/org/apache/poi/hssf/record/SelectionRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,70 +14,80 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; -import java.util.*; - import org.apache.poi.util.LittleEndian; /** - * Title: Selection Record

+ * Title: Selection Record (0x001D)

* Description: shows the user's selection on the sheet * for write set num refs to 0

* - * TODO : Fully implement reference subrecords. * REFERENCE: PG 291 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @author Andrew C. Oliver (acoliver at apache dot org) * @author Jason Height (jheight at chariot dot net dot au) * @author Glen Stampoultzis (glens at apache.org) */ +public final class SelectionRecord extends Record { + public final static short sid = 0x001D; + private byte field_1_pane; + private int field_2_row_active_cell; + private int field_3_col_active_cell; + private int field_4_active_cell_ref_index; + private Reference[] field_6_refs; -public class SelectionRecord - extends Record -{ - public final static short sid = 0x1d; - private byte field_1_pane; - //private short field_2_row_active_cell; - private int field_2_row_active_cell; - private short field_3_col_active_cell; - private short field_4_ref_active_cell; - private short field_5_num_refs; - private ArrayList field_6_refs; // not used yet - + /** + * Note - column values are 8-bit so cannot use CellRangeAddressList + */ public class Reference { - private short field_1_first_row; - private short field_2_last_row; - private byte field_3_first_column; - private byte field_4_last_column; + /* package */ static final int ENCODED_SIZE = 6; + private int _firstRow; + private int _lastRow; + private int _firstCol; + private int _lastCol; - Reference(RecordInputStream in) { - field_1_first_row = in.readShort(); - field_2_last_row = in.readShort(); - field_3_first_column = in.readByte(); - field_4_last_column = in.readByte(); - } - - public short getFirstRow() { - return field_1_first_row; - } - - public short getLastRow() { - return field_2_last_row; - } - - public byte getFirstColumn() { - return field_3_first_column; - } - - public byte getLastColumn() { - return field_4_last_column; - } + /* package */ Reference(int firstRow, int lastRow, int firstColumn, int lastColumn) { + _firstRow = firstRow; + _lastRow = lastRow; + _firstCol = firstColumn; + _lastCol = lastColumn; + } + /* package */ Reference(RecordInputStream in) { + this(in.readUShort(), in.readUShort(), in.readUByte(), in.readUByte()); + } + public void serialize(int offset, byte[] data) { + LittleEndian.putUShort(data, offset + 0, _firstRow); + LittleEndian.putUShort(data, offset + 2, _lastRow); + LittleEndian.putByte(data, offset + 4, _firstCol); + LittleEndian.putByte(data, offset + 6, _lastCol); + } + + public int getFirstRow() { + return _firstRow; + } + public int getLastRow() { + return _lastRow; + } + public int getFirstColumn() { + return _firstCol; + } + public int getLastColumn() { + return _lastCol; + } } - public SelectionRecord() - { + /** + * Creates a default selection record (cell A1, in pane ID 3) + */ + public SelectionRecord(int activeCellRow, int activeCellCol) { + field_1_pane = 3; // pane id 3 is always present. see OOO sec 5.75 'PANE' + field_2_row_active_cell = activeCellRow; + field_3_col_active_cell = activeCellCol; + field_4_active_cell_ref_index = 0; + field_6_refs = new Reference[] { + new Reference(activeCellRow, activeCellRow, activeCellCol, activeCellCol), + }; } /** @@ -86,41 +95,33 @@ public class SelectionRecord * @param in the RecordInputstream to read the record from */ - public SelectionRecord(RecordInputStream in) - { + public SelectionRecord(RecordInputStream in) { super(in); } - protected void validateSid(short id) - { - if (id != sid) - { + protected void validateSid(short id) { + if (id != sid) { throw new RecordFormatException("NOT A valid Selection RECORD"); } } - protected void fillFields(RecordInputStream in) - { + protected void fillFields(RecordInputStream in) { field_1_pane = in.readByte(); - //field_2_row_active_cell = LittleEndian.getShort(data, 1 + offset); field_2_row_active_cell = in.readUShort(); field_3_col_active_cell = in.readShort(); - field_4_ref_active_cell = in.readShort(); - field_5_num_refs = in.readShort(); + field_4_active_cell_ref_index = in.readShort(); + int field_5_num_refs = in.readUShort(); - field_6_refs = new ArrayList(field_5_num_refs); - for (int i=0; i + * See OOO excelfileformat.pdf section 4.14 + * @author Josh Micich + */ +public final class DataValidityTable extends RecordAggregate { + + private static final short sid = -0x01B2; // not a real record + private final DVALRecord _headerRec; + /** + * The list of data validations for the current sheet. + * Note - this may be empty (contrary to OOO documentation) + */ + private final List _validationList; + + public DataValidityTable(RecordStream rs) { + _headerRec = (DVALRecord) rs.getNext(); + List temp = new ArrayList(); + while (rs.peekNextClass() == DVRecord.class) { + temp.add(rs.getNext()); + } + _validationList = temp; + } + + private DataValidityTable() { + _headerRec = new DVALRecord(); + _validationList = new ArrayList(); + } + + public short getSid() { + return sid; + } + + public int serialize(int offset, byte[] data) { + int result = _headerRec.serialize(offset, data); + for (int i = 0; i < _validationList.size(); i++) { + result += ((Record) _validationList.get(i)).serialize(offset + result, data); + } + return result; + } + + public int getRecordSize() { + int result = _headerRec.getRecordSize(); + for (int i = _validationList.size() - 1; i >= 0; i--) { + result += ((Record) _validationList.get(i)).getRecordSize(); + } + return result; + } + + /** + * Creates a new DataValidityTable and inserts it in the right + * place in the sheetRecords list. + */ + public static DataValidityTable createForSheet(List sheetRecords) { + int index = findDVTableInsertPos(sheetRecords); + + DataValidityTable result = new DataValidityTable(); + sheetRecords.add(index, result); + return result; + } + + /** + * Finds the index where the sheet validations header record should be inserted + * @param records the records for this sheet + * + * + WINDOW2 + * o SCL + * o PANE + * oo SELECTION + * o STANDARDWIDTH + * oo MERGEDCELLS + * o LABELRANGES + * o PHONETICPR + * o Conditional Formatting Table + * o Hyperlink Table + * o Data Validity Table + * o SHEETLAYOUT + * o SHEETPROTECTION + * o RANGEPROTECTION + * + EOF + */ + private static int findDVTableInsertPos(List records) { + int i = records.size() - 1; + if (!(records.get(i) instanceof EOFRecord)) { + throw new IllegalStateException("Last sheet record should be EOFRecord"); + } + while (i > 0) { + i--; + Record rec = (Record) records.get(i); + if (isPriorRecord(rec.getSid())) { + Record nextRec = (Record) records.get(i + 1); + if (!isSubsequentRecord(nextRec.getSid())) { + throw new IllegalStateException("Unexpected (" + nextRec.getClass().getName() + + ") found after (" + rec.getClass().getName() + ")"); + } + return i; + } + if (!isSubsequentRecord(rec.getSid())) { + throw new IllegalStateException("Unexpected (" + rec.getClass().getName() + + ") while looking for DV Table insert pos"); + } + } + return 0; + } + + // TODO - add UninterpretedRecord as base class for many of these + // unimplemented sids + + private static boolean isPriorRecord(short sid) { + switch(sid) { + case WindowTwoRecord.sid: + case 0x00A0: // SCL + case PaneRecord.sid: + case SelectionRecord.sid: + case 0x0099: // STANDARDWIDTH + case MergeCellsRecord.sid: + case 0x015F: // LABELRANGES + case 0x00EF: // PHONETICPR + case CFHeaderRecord.sid: + case CFRuleRecord.sid: + case HyperlinkRecord.sid: + case 0x0800: // QUICKTIP + return true; + } + return false; + } + + private static boolean isSubsequentRecord(short sid) { + switch(sid) { + case 0x0862: // SHEETLAYOUT + case 0x0867: // SHEETPROTECTION + case 0x0868: // RANGEPROTECTION + case EOFRecord.sid: + return true; + } + return false; + } + + public void addDataValidation(DVRecord dvRecord) { + _validationList.add(dvRecord); + _headerRec.setDVRecNo(_validationList.size()); + } +} diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java new file mode 100644 index 000000000..3a86871e8 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java @@ -0,0 +1,41 @@ +/* ==================================================================== + 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.record.aggregates; + +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordInputStream; + +/** + * RecordAggregates are groups of of BIFF Records that are typically stored + * together and/or updated together. Workbook / Sheet records are typically stored in a sequential + * list, which does not provide much structure to coordinate updates. + * + * @author Josh Micich + */ +public abstract class RecordAggregate extends Record { + // TODO - convert existing aggregate classes to proper subclasses of this one + protected final void validateSid(short id) { + // TODO - break class hierarchy and make separate from Record + throw new RuntimeException("Should not be called"); + } + protected final void fillFields(RecordInputStream in) { + throw new RuntimeException("Should not be called"); + } + // force subclassses to provide better implementation than default + public abstract int getRecordSize(); +} diff --git a/src/java/org/apache/poi/hssf/record/cf/CellRange.java b/src/java/org/apache/poi/hssf/record/cf/CellRange.java deleted file mode 100644 index 8331fd98f..000000000 --- a/src/java/org/apache/poi/hssf/record/cf/CellRange.java +++ /dev/null @@ -1,513 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.record.cf; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.poi.hssf.util.Region; - -/** - * - * @author Dmitriy Kumshayev - */ -public final class CellRange -{ - /** max 65536 rows in BIFF8 */ - private static final int LAST_ROW_INDEX = 0x00FFFF; - /** max 256 columns in BIFF8 */ - private static final int LAST_COLUMN_INDEX = 0x00FF; - - private static final Region[] EMPTY_REGION_ARRAY = { }; - - private int _firstRow; - private int _lastRow; - private int _firstColumn; - private int _lastColumn; - - /** - * - * @param firstRow - * @param lastRow pass -1 for full column ranges - * @param firstColumn - * @param lastColumn pass -1 for full row ranges - */ - public CellRange(int firstRow, int lastRow, int firstColumn, int lastColumn) - { - if(!isValid(firstRow, lastRow, firstColumn, lastColumn)) { - throw new IllegalArgumentException("invalid cell range (" + firstRow + ", " + lastRow - + ", " + firstColumn + ", " + lastColumn + ")"); - } - _firstRow = firstRow; - _lastRow = convertM1ToMax(lastRow, LAST_ROW_INDEX); - _firstColumn = firstColumn; - _lastColumn = convertM1ToMax(lastColumn, LAST_COLUMN_INDEX); - } - - /** - * Range arithmetic is easier when using a large positive number for 'max row or column' - * instead of -1. - */ - private static int convertM1ToMax(int lastIx, int maxIndex) { - if(lastIx < 0) { - return maxIndex; - } - return lastIx; - } - - public boolean isFullColumnRange() { - return _firstRow == 0 && _lastRow == LAST_ROW_INDEX; - } - public boolean isFullRowRange() { - return _firstColumn == 0 && _lastColumn == LAST_COLUMN_INDEX; - } - - private static CellRange createFromRegion(org.apache.poi.ss.util.Region r) { - return new CellRange(r.getRowFrom(), r.getRowTo(), r.getColumnFrom(), r.getColumnTo()); - } - - private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn) - { - if(lastRow < 0 || lastRow > LAST_ROW_INDEX) { - return false; - } - if(firstRow < 0 || firstRow > LAST_ROW_INDEX) { - return false; - } - - if(lastColumn < 0 || lastColumn > LAST_COLUMN_INDEX) { - return false; - } - if(firstColumn < 0 || firstColumn > LAST_COLUMN_INDEX) { - return false; - } - return true; - } - - public int getFirstRow() - { - return _firstRow; - } - public int getLastRow() - { - return _lastRow; - } - public int getFirstColumn() - { - return _firstColumn; - } - public int getLastColumn() - { - return _lastColumn; - } - - public static final int NO_INTERSECTION = 1; - public static final int OVERLAP = 2; - /** first range is within the second range */ - public static final int INSIDE = 3; - /** first range encloses or is equal to the second */ - public static final int ENCLOSES = 4; - - /** - * Intersect this range with the specified range. - * - * @param another - the specified range - * @return code which reflects how the specified range is related to this range.
- * Possible return codes are: - * NO_INTERSECTION - the specified range is outside of this range;
- * OVERLAP - both ranges partially overlap;
- * INSIDE - the specified range is inside of this one
- * ENCLOSES - the specified range encloses (possibly exactly the same as) this range
- */ - public int intersect(CellRange another ) - { - - int firstRow = another.getFirstRow(); - int lastRow = another.getLastRow(); - int firstCol = another.getFirstColumn(); - int lastCol = another.getLastColumn(); - - if - ( - gt(getFirstRow(),lastRow) || - lt(getLastRow(),firstRow) || - gt(getFirstColumn(),lastCol) || - lt(getLastColumn(),firstCol) - ) - { - return NO_INTERSECTION; - } - else if( contains(another) ) - { - return INSIDE; - } - else if( another.contains(this)) - { - return ENCLOSES; - } - else - { - return OVERLAP; - } - - } - - /** - * Do all possible cell merges between cells of the list so that:
- *

  • if a cell range is completely inside of another cell range, it gets removed from the list - *
  • if two cells have a shared border, merge them into one bigger cell range - * @param cellRangeList - * @return updated List of cell ranges - */ - public static CellRange[] mergeCellRanges(CellRange[] cellRanges) { - if(cellRanges.length < 1) { - return cellRanges; - } - List temp = mergeCellRanges(Arrays.asList(cellRanges)); - return toArray(temp); - } - private static List mergeCellRanges(List cellRangeList) - { - - while(cellRangeList.size() > 1) - { - boolean somethingGotMerged = false; - - for( int i=0; inull if no merge is possible - */ - private static CellRange[] mergeRanges(CellRange range1, CellRange range2) { - - int x = range1.intersect(range2); - switch(x) - { - case CellRange.NO_INTERSECTION: - if( range1.hasExactSharedBorder(range2)) - { - return new CellRange[] { range1.createEnclosingCellRange(range2), }; - } - // else - No intersection and no shared border: do nothing - return null; - case CellRange.OVERLAP: - return resolveRangeOverlap(range1, range2); - case CellRange.INSIDE: - // Remove range2, since it is completely inside of range1 - return new CellRange[] { range1, }; - case CellRange.ENCLOSES: - // range2 encloses range1, so replace it with the enclosing one - return new CellRange[] { range2, }; - } - throw new RuntimeException("unexpected intersection result (" + x + ")"); - } - - // TODO - write junit test for this - static CellRange[] resolveRangeOverlap(CellRange rangeA, CellRange rangeB) { - - if(rangeA.isFullColumnRange()) { - if(rangeB.isFullRowRange()) { - // Excel seems to leave these unresolved - return null; - } - return rangeA.sliceUp(rangeB); - } - if(rangeA.isFullRowRange()) { - if(rangeB.isFullColumnRange()) { - // Excel seems to leave these unresolved - return null; - } - return rangeA.sliceUp(rangeB); - } - if(rangeB.isFullColumnRange()) { - return rangeB.sliceUp(rangeA); - } - if(rangeB.isFullRowRange()) { - return rangeB.sliceUp(rangeA); - } - return rangeA.sliceUp(rangeB); - } - - /** - * @param range never a full row or full column range - * @return an array including this CellRange and all parts of range - * outside of this range - */ - private CellRange[] sliceUp(CellRange range) { - - List temp = new ArrayList(); - - // Chop up range horizontally and vertically - temp.add(range); - if(!isFullColumnRange()) { - temp = cutHorizontally(_firstRow, temp); - temp = cutHorizontally(_lastRow+1, temp); - } - if(!isFullRowRange()) { - temp = cutVertically(_firstColumn, temp); - temp = cutVertically(_lastColumn+1, temp); - } - CellRange[] crParts = toArray(temp); - - // form result array - temp.clear(); - temp.add(this); - - for (int i = 0; i < crParts.length; i++) { - CellRange crPart = crParts[i]; - // only include parts that are not enclosed by this - if(intersect(crPart) != ENCLOSES) { - temp.add(crPart); - } - } - return toArray(temp); - } - - private static List cutHorizontally(int cutRow, List input) { - - List result = new ArrayList(); - CellRange[] crs = toArray(input); - for (int i = 0; i < crs.length; i++) { - CellRange cr = crs[i]; - if(cr._firstRow < cutRow && cutRow < cr._lastRow) { - result.add(new CellRange(cr._firstRow, cutRow, cr._firstColumn, cr._lastColumn)); - result.add(new CellRange(cutRow+1, cr._lastRow, cr._firstColumn, cr._lastColumn)); - } else { - result.add(cr); - } - } - return result; - } - private static List cutVertically(int cutColumn, List input) { - - List result = new ArrayList(); - CellRange[] crs = toArray(input); - for (int i = 0; i < crs.length; i++) { - CellRange cr = crs[i]; - if(cr._firstColumn < cutColumn && cutColumn < cr._lastColumn) { - result.add(new CellRange(cr._firstRow, cr._lastRow, cr._firstColumn, cutColumn)); - result.add(new CellRange(cr._firstRow, cr._lastRow, cutColumn+1, cr._lastColumn)); - } else { - result.add(cr); - } - } - return result; - } - - - private static CellRange[] toArray(List temp) { - CellRange[] result = new CellRange[temp.size()]; - temp.toArray(result); - return result; - } - - /** - * Convert array of regions to a List of CellRange objects - * - * @param regions - * @return List of CellRange objects - */ - public static CellRange[] convertRegionsToCellRanges(org.apache.poi.ss.util.Region[] regions) - { - CellRange[] result = new CellRange[regions.length]; - for( int i=0; itrue if the ranges have a complete shared border (i.e. - * the two ranges together make a simple rectangular region. - */ - public boolean hasExactSharedBorder(CellRange range) - { - int oFirstRow = range._firstRow; - int oLastRow = range._lastRow; - int oFirstCol = range._firstColumn; - int oLastCol = range._lastColumn; - - if (_firstRow > 0 && _firstRow-1 == oLastRow || - oFirstRow > 0 && oFirstRow-1 == _lastRow) { - // ranges have a horizontal border in common - // make sure columns are identical: - return _firstColumn == oFirstCol && _lastColumn == oLastCol; - } - - if (_firstColumn>0 && _firstColumn - 1 == oLastCol || - oFirstCol>0 && _lastColumn == oFirstCol -1) { - // ranges have a vertical border in common - // make sure rows are identical: - return _firstRow == oFirstRow && _lastRow == oLastRow; - } - return false; - } - - /** - * Create an enclosing CellRange for the two cell ranges. - * - * @return enclosing CellRange - */ - public CellRange createEnclosingCellRange(CellRange range) - { - if( range == null) - { - return cloneCellRange(); - } - else - { - CellRange cellRange = - new CellRange( - lt(range.getFirstRow(),getFirstRow())?range.getFirstRow():getFirstRow(), - gt(range.getLastRow(),getLastRow())?range.getLastRow():getLastRow(), - lt(range.getFirstColumn(),getFirstColumn())?range.getFirstColumn():getFirstColumn(), - gt(range.getLastColumn(),getLastColumn())?range.getLastColumn():getLastColumn() - ); - return cellRange; - } - } - - public CellRange cloneCellRange() - { - return new CellRange(getFirstRow(),getLastRow(),getFirstColumn(),getLastColumn()); - } - - /** - * @return true if a < b - */ - private static boolean lt(int a, int b) - { - return a == -1 ? false : (b == -1 ? true : a < b); - } - - /** - * @return true if a <= b - */ - private static boolean le(int a, int b) - { - return a == b || lt(a,b); - } - - /** - * @return true if a > b - */ - private static boolean gt(int a, int b) - { - return lt(b,a); - } - - /** - * @return true if a >= b - */ - private static boolean ge(int a, int b) - { - return !lt(a,b); - } - - public String toString() - { - return "("+getFirstRow()+","+getLastRow()+","+getFirstColumn()+","+getLastColumn()+")"; - } - -} diff --git a/src/java/org/apache/poi/hssf/record/cf/CellRangeUtil.java b/src/java/org/apache/poi/hssf/record/cf/CellRangeUtil.java new file mode 100644 index 000000000..019644c2d --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/cf/CellRangeUtil.java @@ -0,0 +1,363 @@ +/* ==================================================================== + 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.record.cf; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.poi.ss.util.CellRangeAddress; + +/** + * + * @author Dmitriy Kumshayev + */ +public final class CellRangeUtil +{ + + private CellRangeUtil() { + // no instance of this class + } + + public static final int NO_INTERSECTION = 1; + public static final int OVERLAP = 2; + /** first range is within the second range */ + public static final int INSIDE = 3; + /** first range encloses or is equal to the second */ + public static final int ENCLOSES = 4; + + /** + * Intersect this range with the specified range. + * + * @param crB - the specified range + * @return code which reflects how the specified range is related to this range.
    + * Possible return codes are: + * NO_INTERSECTION - the specified range is outside of this range;
    + * OVERLAP - both ranges partially overlap;
    + * INSIDE - the specified range is inside of this one
    + * ENCLOSES - the specified range encloses (possibly exactly the same as) this range
    + */ + public static int intersect(CellRangeAddress crA, CellRangeAddress crB ) + { + + int firstRow = crB.getFirstRow(); + int lastRow = crB.getLastRow(); + int firstCol = crB.getFirstColumn(); + int lastCol = crB.getLastColumn(); + + if + ( + gt(crA.getFirstRow(),lastRow) || + lt(crA.getLastRow(),firstRow) || + gt(crA.getFirstColumn(),lastCol) || + lt(crA.getLastColumn(),firstCol) + ) + { + return NO_INTERSECTION; + } + else if( contains(crA, crB) ) + { + return INSIDE; + } + else if( contains(crB, crA)) + { + return ENCLOSES; + } + else + { + return OVERLAP; + } + + } + + /** + * Do all possible cell merges between cells of the list so that:
    + *
  • if a cell range is completely inside of another cell range, it gets removed from the list + *
  • if two cells have a shared border, merge them into one bigger cell range + * @param cellRangeList + * @return updated List of cell ranges + */ + public static CellRangeAddress[] mergeCellRanges(CellRangeAddress[] cellRanges) { + if(cellRanges.length < 1) { + return cellRanges; + } + List temp = mergeCellRanges(Arrays.asList(cellRanges)); + return toArray(temp); + } + private static List mergeCellRanges(List cellRangeList) + { + + while(cellRangeList.size() > 1) + { + boolean somethingGotMerged = false; + + for( int i=0; inull if no merge is possible + */ + private static CellRangeAddress[] mergeRanges(CellRangeAddress range1, CellRangeAddress range2) { + + int x = intersect(range1, range2); + switch(x) + { + case CellRangeUtil.NO_INTERSECTION: + if(hasExactSharedBorder(range1, range2)) { + return new CellRangeAddress[] { createEnclosingCellRange(range1, range2), }; + } + // else - No intersection and no shared border: do nothing + return null; + case CellRangeUtil.OVERLAP: + return resolveRangeOverlap(range1, range2); + case CellRangeUtil.INSIDE: + // Remove range2, since it is completely inside of range1 + return new CellRangeAddress[] { range1, }; + case CellRangeUtil.ENCLOSES: + // range2 encloses range1, so replace it with the enclosing one + return new CellRangeAddress[] { range2, }; + } + throw new RuntimeException("unexpected intersection result (" + x + ")"); + } + + // TODO - write junit test for this + static CellRangeAddress[] resolveRangeOverlap(CellRangeAddress rangeA, CellRangeAddress rangeB) { + + if(rangeA.isFullColumnRange()) { + if(rangeA.isFullRowRange()) { + // Excel seems to leave these unresolved + return null; + } + return sliceUp(rangeA, rangeB); + } + if(rangeA.isFullRowRange()) { + if(rangeB.isFullColumnRange()) { + // Excel seems to leave these unresolved + return null; + } + return sliceUp(rangeA, rangeB); + } + if(rangeB.isFullColumnRange()) { + return sliceUp(rangeB, rangeA); + } + if(rangeB.isFullRowRange()) { + return sliceUp(rangeB, rangeA); + } + return sliceUp(rangeA, rangeB); + } + + /** + * @param crB never a full row or full column range + * @return an array including this CellRange and all parts of range + * outside of this range + */ + private static CellRangeAddress[] sliceUp(CellRangeAddress crA, CellRangeAddress crB) { + + List temp = new ArrayList(); + + // Chop up range horizontally and vertically + temp.add(crB); + if(!crA.isFullColumnRange()) { + temp = cutHorizontally(crA.getFirstRow(), temp); + temp = cutHorizontally(crA.getLastRow()+1, temp); + } + if(!crA.isFullRowRange()) { + temp = cutVertically(crA.getFirstColumn(), temp); + temp = cutVertically(crA.getLastColumn()+1, temp); + } + CellRangeAddress[] crParts = toArray(temp); + + // form result array + temp.clear(); + temp.add(crA); + + for (int i = 0; i < crParts.length; i++) { + CellRangeAddress crPart = crParts[i]; + // only include parts that are not enclosed by this + if(intersect(crA, crPart) != ENCLOSES) { + temp.add(crPart); + } + } + return toArray(temp); + } + + private static List cutHorizontally(int cutRow, List input) { + + List result = new ArrayList(); + CellRangeAddress[] crs = toArray(input); + for (int i = 0; i < crs.length; i++) { + CellRangeAddress cr = crs[i]; + if(cr.getFirstRow() < cutRow && cutRow < cr.getLastRow()) { + result.add(new CellRangeAddress(cr.getFirstRow(), cutRow, cr.getFirstColumn(), cr.getLastColumn())); + result.add(new CellRangeAddress(cutRow+1, cr.getLastRow(), cr.getFirstColumn(), cr.getLastColumn())); + } else { + result.add(cr); + } + } + return result; + } + private static List cutVertically(int cutColumn, List input) { + + List result = new ArrayList(); + CellRangeAddress[] crs = toArray(input); + for (int i = 0; i < crs.length; i++) { + CellRangeAddress cr = crs[i]; + if(cr.getFirstColumn() < cutColumn && cutColumn < cr.getLastColumn()) { + result.add(new CellRangeAddress(cr.getFirstRow(), cr.getLastRow(), cr.getFirstColumn(), cutColumn)); + result.add(new CellRangeAddress(cr.getFirstRow(), cr.getLastRow(), cutColumn+1, cr.getLastColumn())); + } else { + result.add(cr); + } + } + return result; + } + + + private static CellRangeAddress[] toArray(List temp) { + CellRangeAddress[] result = new CellRangeAddress[temp.size()]; + temp.toArray(result); + return result; + } + + + + /** + * Check if the specified range is located inside of this cell range. + * + * @param crB + * @return true if this cell range contains the argument range inside if it's area + */ + public static boolean contains(CellRangeAddress crA, CellRangeAddress crB) + { + int firstRow = crB.getFirstRow(); + int lastRow = crB.getLastRow(); + int firstCol = crB.getFirstColumn(); + int lastCol = crB.getLastColumn(); + return le(crA.getFirstRow(), firstRow) && ge(crA.getLastRow(), lastRow) + && le(crA.getFirstColumn(), firstCol) && ge(crA.getLastColumn(), lastCol); + } + + /** + * Check if the specified cell range has a shared border with the current range. + * + * @return true if the ranges have a complete shared border (i.e. + * the two ranges together make a simple rectangular region. + */ + public static boolean hasExactSharedBorder(CellRangeAddress crA, CellRangeAddress crB) { + int oFirstRow = crB.getFirstRow(); + int oLastRow = crB.getLastRow(); + int oFirstCol = crB.getFirstColumn(); + int oLastCol = crB.getLastColumn(); + + if (crA.getFirstRow() > 0 && crA.getFirstRow()-1 == oLastRow || + oFirstRow > 0 && oFirstRow-1 == crA.getLastRow()) { + // ranges have a horizontal border in common + // make sure columns are identical: + return crA.getFirstColumn() == oFirstCol && crA.getLastColumn() == oLastCol; + } + + if (crA.getFirstColumn()>0 && crA.getFirstColumn() - 1 == oLastCol || + oFirstCol>0 && crA.getLastColumn() == oFirstCol -1) { + // ranges have a vertical border in common + // make sure rows are identical: + return crA.getFirstRow() == oFirstRow && crA.getLastRow() == oLastRow; + } + return false; + } + + /** + * Create an enclosing CellRange for the two cell ranges. + * + * @return enclosing CellRange + */ + public static CellRangeAddress createEnclosingCellRange(CellRangeAddress crA, CellRangeAddress crB) { + if( crB == null) { + return crA.copy(); + } + + return + new CellRangeAddress( + lt(crB.getFirstRow(), crA.getFirstRow()) ?crB.getFirstRow() :crA.getFirstRow(), + gt(crB.getLastRow(), crA.getLastRow()) ?crB.getLastRow() :crA.getLastRow(), + lt(crB.getFirstColumn(),crA.getFirstColumn())?crB.getFirstColumn():crA.getFirstColumn(), + gt(crB.getLastColumn(), crA.getLastColumn()) ?crB.getLastColumn() :crA.getLastColumn() + ); + + } + + /** + * @return true if a < b + */ + private static boolean lt(int a, int b) + { + return a == -1 ? false : (b == -1 ? true : a < b); + } + + /** + * @return true if a <= b + */ + private static boolean le(int a, int b) + { + return a == b || lt(a,b); + } + + /** + * @return true if a > b + */ + private static boolean gt(int a, int b) + { + return lt(b,a); + } + + /** + * @return true if a >= b + */ + private static boolean ge(int a, int b) + { + return !lt(a,b); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java b/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java index dc28e705a..f02275860 100644 --- a/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java @@ -36,7 +36,7 @@ public final class NumberPtg extends ScalarConstantPtg { /** Create a NumberPtg from a byte array read from disk */ public NumberPtg(RecordInputStream in) { - field_1_value = in.readDouble(); + this(in.readDouble()); } /** Create a NumberPtg from a string representation of the number @@ -45,9 +45,12 @@ public final class NumberPtg extends ScalarConstantPtg { * @param value : String representation of a floating point number */ public NumberPtg(String value) { - field_1_value = Double.parseDouble(value); + this(Double.parseDouble(value)); } + public NumberPtg(double value) { + field_1_value = value; + } public double getValue() { @@ -67,6 +70,15 @@ public final class NumberPtg extends ScalarConstantPtg { public String toFormulaString(Workbook book) { - return "" + getValue(); + // TODO - java's rendering of double values is not quite same as excel's + return String.valueOf(field_1_value); + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(field_1_value); + sb.append("]"); + return sb.toString(); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index 69464f8f4..50f3450d2 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -41,6 +41,7 @@ import org.apache.poi.ss.usermodel.Workbook; * @author Jason Height (jheight at chariot dot net dot au) */ public abstract class Ptg implements Cloneable { + public static final Ptg[] EMPTY_PTG_ARRAY = { }; /* convert infix order ptg list to rpn order ptg list * @return List ptgs in RPN order @@ -250,6 +251,9 @@ public abstract class Ptg implements Cloneable { } } private static Ptg[] toPtgArray(List l) { + if (l.isEmpty()) { + return EMPTY_PTG_ARRAY; + } Ptg[] result = new Ptg[l.size()]; l.toArray(result); return result; diff --git a/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java b/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java new file mode 100644 index 000000000..a1027ccfa --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java @@ -0,0 +1,479 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.usermodel; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.record.formula.NumberPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.StringPtg; + +/** + * + * @author Josh Micich + */ +public class DVConstraint { + /** + * ValidationType enum + */ + public static final class ValidationType { + private ValidationType() { + // no instances of this class + } + /** 'Any value' type - value not restricted */ + public static final int ANY = 0x00; + /** Integer ('Whole number') type */ + public static final int INTEGER = 0x01; + /** Decimal type */ + public static final int DECIMAL = 0x02; + /** List type ( combo box type ) */ + public static final int LIST = 0x03; + /** Date type */ + public static final int DATE = 0x04; + /** Time type */ + public static final int TIME = 0x05; + /** String length type */ + public static final int TEXT_LENGTH = 0x06; + /** Formula ( 'Custom' ) type */ + public static final int FORMULA = 0x07; + } + /** + * Condition operator enum + */ + public static final class OperatorType { + private OperatorType() { + // no instances of this class + } + + public static final int BETWEEN = 0x00; + public static final int NOT_BETWEEN = 0x01; + public static final int EQUAL = 0x02; + public static final int NOT_EQUAL = 0x03; + public static final int GREATER_THAN = 0x04; + public static final int LESS_THAN = 0x05; + public static final int GREATER_OR_EQUAL = 0x06; + public static final int LESS_OR_EQUAL = 0x07; + /** default value to supply when the operator type is not used */ + public static final int IGNORED = BETWEEN; + + /* package */ static void validateSecondArg(int comparisonOperator, String paramValue) { + switch (comparisonOperator) { + case BETWEEN: + case NOT_BETWEEN: + if (paramValue == null) { + throw new IllegalArgumentException("expr2 must be supplied for 'between' comparisons"); + } + // all other operators don't need second arg + } + } + } + + /* package */ static final class FormulaPair { + + private final Ptg[] _formula1; + private final Ptg[] _formula2; + + public FormulaPair(Ptg[] formula1, Ptg[] formula2) { + _formula1 = formula1; + _formula2 = formula2; + } + public Ptg[] getFormula1() { + return _formula1; + } + public Ptg[] getFormula2() { + return _formula2; + } + + } + + // convenient access to ValidationType namespace + private static final ValidationType VT = null; + + + private final int _validationType; + private int _operator; + private String[] _explicitListValues; + + private String _formula1; + private String _formula2; + private Double _value1; + private Double _value2; + + + private DVConstraint(int validationType, int comparisonOperator, String formulaA, + String formulaB, Double value1, Double value2, String[] excplicitListValues) { + _validationType = validationType; + _operator = comparisonOperator; + _formula1 = formulaA; + _formula2 = formulaB; + _value1 = value1; + _value2 = value2; + _explicitListValues = excplicitListValues; + } + + + /** + * Creates a list constraint + */ + private DVConstraint(String listFormula, String[] excplicitListValues) { + this(ValidationType.LIST, OperatorType.IGNORED, + listFormula, null, null, null, excplicitListValues); + } + + /** + * Creates a number based data validation constraint. The text values entered for expr1 and expr2 + * can be either standard Excel formulas or formatted number values. If the expression starts + * with '=' it is parsed as a formula, otherwise it is parsed as a formatted number. + * + * @param validationType one of {@link ValidationType#ANY}, {@link ValidationType#DECIMAL}, + * {@link ValidationType#INTEGER}, {@link ValidationType#TEXT_LENGTH} + * @param comparisonOperator any constant from {@link OperatorType} enum + * @param expr1 date formula (when first char is '=') or formatted number value + * @param expr2 date formula (when first char is '=') or formatted number value + */ + public static DVConstraint createNumericConstraint(int validationType, int comparisonOperator, + String expr1, String expr2) { + switch (validationType) { + case ValidationType.ANY: + if (expr1 != null || expr2 != null) { + throw new IllegalArgumentException("expr1 and expr2 must be null for validation type 'any'"); + } + break; + case ValidationType.DECIMAL: + case ValidationType.INTEGER: + case ValidationType.TEXT_LENGTH: + if (expr1 == null) { + throw new IllegalArgumentException("expr1 must be supplied"); + } + OperatorType.validateSecondArg(comparisonOperator, expr2); + break; + default: + throw new IllegalArgumentException("Validation Type (" + + validationType + ") not supported with this method"); + } + // formula1 and value1 are mutually exclusive + String formula1 = getFormulaFromTextExpression(expr1); + Double value1 = formula1 == null ? convertNumber(expr1) : null; + // formula2 and value2 are mutually exclusive + String formula2 = getFormulaFromTextExpression(expr2); + Double value2 = formula2 == null ? convertNumber(expr2) : null; + return new DVConstraint(validationType, comparisonOperator, formula1, formula2, value1, value2, null); + } + + public static DVConstraint createFormulaListConstraint(String listFormula) { + return new DVConstraint(listFormula, null); + } + public static DVConstraint createExplicitListConstraint(String[] explicitListValues) { + return new DVConstraint(null, explicitListValues); + } + + + /** + * Creates a time based data validation constraint. The text values entered for expr1 and expr2 + * can be either standard Excel formulas or formatted time values. If the expression starts + * with '=' it is parsed as a formula, otherwise it is parsed as a formatted time. To parse + * formatted times, two formats are supported: "HH:MM" or "HH:MM:SS". This is contrary to + * Excel which uses the default time format from the OS. + * + * @param comparisonOperator constant from {@link OperatorType} enum + * @param expr1 date formula (when first char is '=') or formatted time value + * @param expr2 date formula (when first char is '=') or formatted time value + */ + public static DVConstraint createTimeConstraint(int comparisonOperator, String expr1, String expr2) { + if (expr1 == null) { + throw new IllegalArgumentException("expr1 must be supplied"); + } + OperatorType.validateSecondArg(comparisonOperator, expr1); + + // formula1 and value1 are mutually exclusive + String formula1 = getFormulaFromTextExpression(expr1); + Double value1 = formula1 == null ? convertTime(expr1) : null; + // formula2 and value2 are mutually exclusive + String formula2 = getFormulaFromTextExpression(expr2); + Double value2 = formula2 == null ? convertTime(expr2) : null; + return new DVConstraint(VT.TIME, comparisonOperator, formula1, formula2, value1, value2, null); + + } + /** + * Creates a date based data validation constraint. The text values entered for expr1 and expr2 + * can be either standard Excel formulas or formatted date values. If the expression starts + * with '=' it is parsed as a formula, otherwise it is parsed as a formatted date (Excel uses + * the same convention). To parse formatted dates, a date format needs to be specified. This + * is contrary to Excel which uses the default short date format from the OS. + * + * @param comparisonOperator constant from {@link OperatorType} enum + * @param expr1 date formula (when first char is '=') or formatted date value + * @param expr2 date formula (when first char is '=') or formatted date value + * @param dateFormat ignored if both expr1 and expr2 are formulas. Default value is "YYYY/MM/DD" + * otherwise any other valid argument for SimpleDateFormat can be used + * @see SimpleDateFormat + */ + public static DVConstraint createDateConstraint(int comparisonOperator, String expr1, String expr2, String dateFormat) { + if (expr1 == null) { + throw new IllegalArgumentException("expr1 must be supplied"); + } + OperatorType.validateSecondArg(comparisonOperator, expr2); + SimpleDateFormat df = dateFormat == null ? null : new SimpleDateFormat(dateFormat); + + // formula1 and value1 are mutually exclusive + String formula1 = getFormulaFromTextExpression(expr1); + Double value1 = formula1 == null ? convertDate(expr1, df) : null; + // formula2 and value2 are mutually exclusive + String formula2 = getFormulaFromTextExpression(expr2); + Double value2 = formula2 == null ? convertDate(expr2, df) : null; + return new DVConstraint(VT.DATE, comparisonOperator, formula1, formula2, value1, value2, null); + } + + /** + * Distinguishes formula expressions from simple value expressions. This logic is only + * required by a few factory methods in this class that create data validation constraints + * from more or less the same parameters that would have been entered in the Excel UI. The + * data validation dialog box uses the convention that formulas begin with '='. Other methods + * in this class follow the POI convention (formulas and values are distinct), so the '=' + * convention is not used there. + * + * @param textExpr a formula or value expression + * @return all text after '=' if textExpr begins with '='. Otherwise null if textExpr does not begin with '=' + */ + private static String getFormulaFromTextExpression(String textExpr) { + if (textExpr == null) { + return null; + } + if (textExpr.length() < 1) { + throw new IllegalArgumentException("Empty string is not a valid formula/value expression"); + } + if (textExpr.charAt(0) == '=') { + return textExpr.substring(1); + } + return null; + } + + + /** + * @return null if numberStr is null + */ + private static Double convertNumber(String numberStr) { + if (numberStr == null) { + return null; + } + try { + return new Double(numberStr); + } catch (NumberFormatException e) { + throw new RuntimeException("The supplied text '" + numberStr + + "' could not be parsed as a number"); + } + } + + /** + * @return null if timeStr is null + */ + private static Double convertTime(String timeStr) { + if (timeStr == null) { + return null; + } + return new Double(HSSFDateUtil.convertTime(timeStr)); + } + /** + * @param dateFormat pass null for default YYYYMMDD + * @return null if timeStr is null + */ + private static Double convertDate(String dateStr, SimpleDateFormat dateFormat) { + if (dateStr == null) { + return null; + } + Date dateVal; + if (dateFormat == null) { + dateVal = HSSFDateUtil.parseYYYYMMDDDate(dateStr); + } else { + try { + dateVal = dateFormat.parse(dateStr); + } catch (ParseException e) { + throw new RuntimeException("Failed to parse date '" + dateStr + + "' using specified format '" + dateFormat + "'", e); + } + } + return new Double(HSSFDateUtil.getExcelDate(dateVal)); + } + + public static DVConstraint createCustomFormulaConstraint(String formula) { + if (formula == null) { + throw new IllegalArgumentException("formula must be supplied"); + } + return new DVConstraint(VT.FORMULA, OperatorType.IGNORED, formula, null, null, null, null); + } + + /** + * @return both parsed formulas (for expression 1 and 2). + */ + /* package */ FormulaPair createFormulas(HSSFWorkbook workbook) { + Ptg[] formula1; + Ptg[] formula2; + if (isListValidationType()) { + formula1 = createListFormula(workbook); + formula2 = Ptg.EMPTY_PTG_ARRAY; + } else { + formula1 = convertDoubleFormula(_formula1, _value1, workbook); + formula2 = convertDoubleFormula(_formula2, _value2, workbook); + } + return new FormulaPair(formula1, formula2); + } + + private Ptg[] createListFormula(HSSFWorkbook workbook) { + + if (_explicitListValues == null) { + // formula is parsed with slightly different RVA rules: (root node type must be 'reference') + return FormulaParser.parse(_formula1, workbook, FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST); + // To do: Excel places restrictions on the available operations within a list formula. + // Some things like union and intersection are not allowed. + } + // explicit list was provided + StringBuffer sb = new StringBuffer(_explicitListValues.length * 16); + for (int i = 0; i < _explicitListValues.length; i++) { + if (i > 0) { + sb.append('\0'); // list delimiter is the nul char + } + sb.append(_explicitListValues[i]); + + } + return new Ptg[] { new StringPtg(sb.toString()), }; + } + + /** + * @return The parsed token array representing the formula or value specified. + * Empty array if both formula and value are null + */ + private static Ptg[] convertDoubleFormula(String formula, Double value, HSSFWorkbook workbook) { + if (formula == null) { + if (value == null) { + return Ptg.EMPTY_PTG_ARRAY; + } + return new Ptg[] { new NumberPtg(value.doubleValue()), }; + } + if (value != null) { + throw new IllegalStateException("Both formula and value cannot be present"); + } + return FormulaParser.parse(formula, workbook); + } + + + /** + * @return data validation type of this constraint + * @see ValidationType + */ + public int getValidationType() { + return _validationType; + } + /** + * Convenience method + * @return true if this constraint is a 'list' validation + */ + public boolean isListValidationType() { + return _validationType == VT.LIST; + } + /** + * Convenience method + * @return true if this constraint is a 'list' validation with explicit values + */ + public boolean isExplicitList() { + return _validationType == VT.LIST && _explicitListValues != null; + } + /** + * @return the operator used for this constraint + * @see OperatorType + */ + public int getOperator() { + return _operator; + } + /** + * Sets the comparison operator for this constraint + * @see OperatorType + */ + public void setOperator(int operator) { + _operator = operator; + } + + public String[] getExplicitListValues() { + return _explicitListValues; + } + public void setExplicitListValues(String[] explicitListValues) { + if (_validationType != VT.LIST) { + throw new RuntimeException("Cannot setExplicitListValues on non-list constraint"); + } + _formula1 = null; + _explicitListValues = explicitListValues; + } + + /** + * @return the formula for expression 1. May be null + */ + public String getFormula1() { + return _formula1; + } + /** + * Sets a formula for expression 1. + */ + public void setFormula1(String formula1) { + _value1 = null; + _explicitListValues = null; + _formula1 = formula1; + } + + /** + * @return the formula for expression 2. May be null + */ + public String getFormula2() { + return _formula2; + } + /** + * Sets a formula for expression 2. + */ + public void setFormula2(String formula2) { + _value2 = null; + _formula2 = formula2; + } + + /** + * @return the numeric value for expression 1. May be null + */ + public Double getValue1() { + return _value1; + } + /** + * Sets a numeric value for expression 1. + */ + public void setValue1(double value1) { + _formula1 = null; + _value1 = new Double(value1); + } + + /** + * @return the numeric value for expression 2. May be null + */ + public Double getValue2() { + return _value2; + } + /** + * Sets a numeric value for expression 2. + */ + public void setValue2(double value2) { + _formula2 = null; + _value2 = new Double(value2); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 3283f98dd..f110d71f9 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -34,7 +34,24 @@ import java.util.Iterator; import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.BlankRecord; +import org.apache.poi.hssf.record.BoolErrRecord; +import org.apache.poi.hssf.record.CellValueRecordInterface; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.DrawingRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.ExtendedFormatRecord; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.HyperlinkRecord; +import org.apache.poi.hssf.record.LabelSSTRecord; +import org.apache.poi.hssf.record.NoteRecord; +import org.apache.poi.hssf.record.NumberRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.SubRecord; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.hssf.record.UnicodeString; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.ss.usermodel.Cell; @@ -1055,11 +1072,18 @@ public class HSSFCell implements Cell } /** - * Assign a comment to this cell + * Assign a comment to this cell. If the supplied + * comment is null, the comment for this cell + * will be removed. * * @param comment comment associated with this cell */ public void setCellComment(Comment comment){ + if(comment == null) { + removeCellComment(); + return; + } + this.comment = (HSSFComment) comment; this.comment.setRow((short)record.getRow()); this.comment.setColumn(record.getColumn()); @@ -1076,6 +1100,49 @@ public class HSSFCell implements Cell } return comment; } + + /** + * Removes the comment for this cell, if + * there is one. + * WARNING - some versions of excel will loose + * all comments after performing this action! + */ + public void removeCellComment() { + HSSFComment comment = findCellComment(sheet, record.getRow(), record.getColumn()); + this.comment = null; + + if(comment == null) { + // Nothing to do + return; + } + + // Zap the underlying NoteRecord + sheet.getRecords().remove(comment.getNoteRecord()); + + // If we have a TextObjectRecord, is should + // be proceeed by: + // MSODRAWING with container + // OBJ + // MSODRAWING with EscherTextboxRecord + if(comment.getTextObjectRecord() != null) { + TextObjectRecord txo = comment.getTextObjectRecord(); + int txoAt = sheet.getRecords().indexOf(txo); + + if(sheet.getRecords().get(txoAt-3) instanceof DrawingRecord && + sheet.getRecords().get(txoAt-2) instanceof ObjRecord && + sheet.getRecords().get(txoAt-1) instanceof DrawingRecord) { + // Zap these, in reverse order + sheet.getRecords().remove(txoAt-1); + sheet.getRecords().remove(txoAt-2); + sheet.getRecords().remove(txoAt-3); + } else { + throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment"); + } + + // Now remove the text record + sheet.getRecords().remove(txo); + } + } /** * Cell comment finder. diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java index 85eca2d47..0c8a16e42 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -156,4 +156,13 @@ public class HSSFComment extends HSSFTextbox implements Comment { } super.setString(string); } + + /** + * Returns the underlying Note record + */ + protected NoteRecord getNoteRecord() { return note; } + /** + * Returns the underlying Text record + */ + protected TextObjectRecord getTextObjectRecord() { return txo; } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormatting.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormatting.java index 6c798abcf..3029515f4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormatting.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormatting.java @@ -16,11 +16,10 @@ ==================================================================== */ package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.record.CFHeaderRecord; import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; -import org.apache.poi.hssf.record.cf.CellRange; -import org.apache.poi.hssf.util.Region; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.Region; /** * HSSFConditionalFormatting class encapsulates all settings of Conditional Formatting. @@ -96,13 +95,18 @@ public final class HSSFConditionalFormatting } /** - * @return array of Regions. never null + * @deprecated (Aug-2008) use {@link HSSFConditionalFormatting#getFormattingRanges()} */ public Region[] getFormattingRegions() { - CFHeaderRecord cfh = cfAggregate.getHeader(); - CellRange[] cellRanges = cfh.getCellRanges(); - return CellRange.convertCellRangesToRegions(cellRanges); + CellRangeAddress[] cellRanges = getFormattingRanges(); + return Region.convertCellRangesToRegions(cellRanges); + } + /** + * @return array of CellRangeAddresss. never null + */ + public CellRangeAddress[] getFormattingRanges() { + return cfAggregate.getHeader().getCellRanges(); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDataValidation.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDataValidation.java new file mode 100644 index 000000000..0591158b6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDataValidation.java @@ -0,0 +1,235 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.usermodel; + +import org.apache.poi.hssf.record.DVRecord; +import org.apache.poi.hssf.usermodel.DVConstraint.FormulaPair; +import org.apache.poi.ss.util.CellRangeAddressList; + +/** + *Utility class for creating data validation cells + * + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + */ +public final class HSSFDataValidation { + /** + * Error style constants for error box + */ + public static final class ErrorStyle { + /** STOP style */ + public static final int STOP = 0x00; + /** WARNING style */ + public static final int WARNING = 0x01; + /** INFO style */ + public static final int INFO = 0x02; + } + + private String _prompt_title; + private String _prompt_text; + private String _error_title; + private String _error_text; + + private int _errorStyle = ErrorStyle.STOP; + private boolean _emptyCellAllowed = true; + private boolean _suppress_dropdown_arrow = false; + private boolean _showPromptBox = true; + private boolean _showErrorBox = true; + private final CellRangeAddressList _regions; + private DVConstraint _constraint; + + /** + * Constructor which initializes the cell range on which this object will be + * applied + * @param constraint + */ + public HSSFDataValidation(CellRangeAddressList regions, DVConstraint constraint) { + _regions = regions; + _constraint = constraint; + } + + + public DVConstraint getConstraint() { + return _constraint; + } + + /** + * Sets the error style for error box + * @see ErrorStyle + */ + public void setErrorStyle(int error_style) { + _errorStyle = error_style; + } + + /** + * @return the error style of error box + * @see ErrorStyle + */ + public int getErrorStyle() { + return _errorStyle; + } + + /** + * Sets if this object allows empty as a valid value + * + * @param allowed true if this object should treats empty as valid value , false + * otherwise + */ + public void setEmptyCellAllowed(boolean allowed) { + _emptyCellAllowed = allowed; + } + + /** + * Retrieve the settings for empty cells allowed + * + * @return True if this object should treats empty as valid value , false + * otherwise + */ + public boolean getEmptyCellAllowed() { + return _emptyCellAllowed; + } + + /** + * Useful for list validation objects . + * + * @param suppress + * True if a list should display the values into a drop down list , + * false otherwise . In other words , if a list should display + * the arrow sign on its right side + */ + public void setSuppressDropDownArrow(boolean suppress) { + _suppress_dropdown_arrow = suppress; + } + + /** + * Useful only list validation objects . This method always returns false if + * the object isn't a list validation object + * + * @return true if a list should display the values into a drop down list , + * false otherwise . + */ + public boolean getSuppressDropDownArrow() { + if (_constraint.isListValidationType()) { + return _suppress_dropdown_arrow; + } + return false; + } + + /** + * Sets the behaviour when a cell which belongs to this object is selected + * + * @param show true if an prompt box should be displayed , false otherwise + */ + public void setShowPromptBox(boolean show) { + _showPromptBox = show; + } + + /** + * @param show true if an prompt box should be displayed , false otherwise + */ + public boolean getShowPromptBox() { + return _showPromptBox; + } + + /** + * Sets the behaviour when an invalid value is entered + * + * @param show true if an error box should be displayed , false otherwise + */ + public void setShowErrorBox(boolean show) { + _showErrorBox = show; + } + + /** + * @return true if an error box should be displayed , false otherwise + */ + public boolean getShowErrorBox() { + return _showErrorBox; + } + + + /** + * Sets the title and text for the prompt box . Prompt box is displayed when + * the user selects a cell which belongs to this validation object . In + * order for a prompt box to be displayed you should also use method + * setShowPromptBox( boolean show ) + * + * @param title The prompt box's title + * @param text The prompt box's text + */ + public void createPromptBox(String title, String text) { + _prompt_title = title; + _prompt_text = text; + this.setShowPromptBox(true); + } + + /** + * @return Prompt box's title or null + */ + public String getPromptBoxTitle() { + return _prompt_title; + } + + /** + * @return Prompt box's text or null + */ + public String getPromptBoxText() { + return _prompt_text; + } + + /** + * Sets the title and text for the error box . Error box is displayed when + * the user enters an invalid value int o a cell which belongs to this + * validation object . In order for an error box to be displayed you should + * also use method setShowErrorBox( boolean show ) + * + * @param title The error box's title + * @param text The error box's text + */ + public void createErrorBox(String title, String text) { + _error_title = title; + _error_text = text; + this.setShowErrorBox(true); + } + + /** + * @return Error box's title or null + */ + public String getErrorBoxTitle() { + return _error_title; + } + + /** + * @return Error box's text or null + */ + public String getErrorBoxText() { + return _error_text; + } + + public DVRecord createDVRecord(HSSFWorkbook workbook) { + + FormulaPair fp = _constraint.createFormulas(workbook); + + return new DVRecord(_constraint.getValidationType(), + _constraint.getOperator(), + _errorStyle, _emptyCellAllowed, getSuppressDropDownArrow(), + _constraint.isExplicitList(), + _showPromptBox, _prompt_title, _prompt_text, + _showErrorBox, _error_title, _error_text, + fp.getFormula1(), fp.getFormula2(), + _regions); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index d4bc9613d..dcf68b9cf 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -28,7 +28,6 @@ import java.text.NumberFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Stack; import java.util.TreeMap; import org.apache.poi.ddf.EscherRecord; @@ -49,13 +48,13 @@ import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.WSBoolRecord; import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.RefPtg; -import org.apache.poi.hssf.util.HSSFCellRangeAddress; -import org.apache.poi.hssf.util.HSSFDataValidation; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.hssf.util.Region; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -393,92 +392,19 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet /** * Creates a data validation object - * @param obj_validation The Data validation object settings + * @param dataValidation The Data validation object settings */ - public void addValidationData(HSSFDataValidation obj_validation) - { - if ( obj_validation == null ) - { - return; + public void addValidationData(HSSFDataValidation dataValidation) { + if (dataValidation == null) { + throw new IllegalArgumentException("objValidation must not be null"); } - DVALRecord dvalRec = (DVALRecord)sheet.findFirstRecordBySid( DVALRecord.sid ); - int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid ); - if ( dvalRec == null ) - { - dvalRec = new DVALRecord(); - sheet.getRecords().add( eofLoc, dvalRec ); - } - int curr_dvRecNo = dvalRec.getDVRecNo(); - dvalRec.setDVRecNo(curr_dvRecNo+1); + DataValidityTable dvt = sheet.getOrCreateDataValidityTable(); - //create dv record - DVRecord dvRecord = new DVRecord(); - - //dv record's option flags - dvRecord.setDataType( obj_validation.getDataValidationType() ); - dvRecord.setErrorStyle(obj_validation.getErrorStyle()); - dvRecord.setEmptyCellAllowed(obj_validation.getEmptyCellAllowed()); - dvRecord.setSurppresDropdownArrow(obj_validation.getSurppressDropDownArrow()); - dvRecord.setShowPromptOnCellSelected(obj_validation.getShowPromptBox()); - dvRecord.setShowErrorOnInvalidValue(obj_validation.getShowErrorBox()); - dvRecord.setConditionOperator(obj_validation.getOperator()); - - //string fields - dvRecord.setStringField( DVRecord.STRING_PROMPT_TITLE,obj_validation.getPromptBoxTitle()); - dvRecord.setStringField( DVRecord.STRING_PROMPT_TEXT, obj_validation.getPromptBoxText()); - dvRecord.setStringField( DVRecord.STRING_ERROR_TITLE, obj_validation.getErrorBoxTitle()); - dvRecord.setStringField( DVRecord.STRING_ERROR_TEXT, obj_validation.getErrorBoxText()); - - //formula fields ( size and data ) - String str_formula = obj_validation.getFirstFormula(); - FormulaParser fp = new FormulaParser(str_formula, workbook); - fp.parse(); - Stack ptg_arr = new Stack(); - Ptg[] ptg = fp.getRPNPtg(); - int size = 0; - for (int k = 0; k < ptg.length; k++) - { - if ( ptg[k] instanceof org.apache.poi.hssf.record.formula.AreaPtg ) - { - //we should set ptgClass to Ptg.CLASS_REF and explicit formula string to false - ptg[k].setClass(Ptg.CLASS_REF); - obj_validation.setExplicitListFormula(false); - } - size += ptg[k].getSize(); - ptg_arr.push(ptg[k]); - } - dvRecord.setFirstFormulaRPN(ptg_arr); - dvRecord.setFirstFormulaSize((short)size); - - dvRecord.setListExplicitFormula(obj_validation.getExplicitListFormula()); - - if ( obj_validation.getSecondFormula() != null ) - { - str_formula = obj_validation.getSecondFormula(); - fp = new FormulaParser(str_formula, workbook); - fp.parse(); - ptg_arr = new Stack(); - ptg = fp.getRPNPtg(); - size = 0; - for (int k = 0; k < ptg.length; k++) - { - size += ptg[k].getSize(); - ptg_arr.push(ptg[k]); - } - dvRecord.setSecFormulaRPN(ptg_arr); - dvRecord.setSecFormulaSize((short)size); - } - - //dv records cell range field - HSSFCellRangeAddress cell_range = new HSSFCellRangeAddress(); - cell_range.addADDRStructure(obj_validation.getFirstRow(), obj_validation.getFirstColumn(), obj_validation.getLastRow(), obj_validation.getLastColumn()); - dvRecord.setCellRangeAddress(cell_range); - - //add dv record - eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid ); - sheet.getRecords().add( eofLoc, dvRecord ); + DVRecord dvRecord = dataValidation.createDVRecord(workbook); + dvt.addDataValidation(dvRecord); } + /** * Get the visibility state for a given column. * @param column - the column to get (0-based) @@ -610,19 +536,28 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet } /** - * adds a merged region of cells (hence those cells form one) - * @param region (rowfrom/colfrom-rowto/colto) to merge - * @return index of this region + * @deprecated (Aug-2008) use CellRangeAddress instead of Region */ public int addMergedRegion(org.apache.poi.ss.util.Region region) { - //return sheet.addMergedRegion((short) region.getRowFrom(), return sheet.addMergedRegion( region.getRowFrom(), region.getColumnFrom(), //(short) region.getRowTo(), region.getRowTo(), region.getColumnTo()); } + /** + * adds a merged region of cells (hence those cells form one) + * @param region (rowfrom/colfrom-rowto/colto) to merge + * @return index of this region + */ + public int addMergedRegion(CellRangeAddress region) + { + return sheet.addMergedRegion( region.getFirstRow(), + region.getFirstColumn(), + region.getLastRow(), + region.getLastColumn()); + } /** * Whether a record must be inserted or not at generation to indicate that @@ -659,7 +594,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet /** * TODO: Boolean not needed, remove after next release - * @deprecated use getVerticallyCenter() instead + * @deprecated (Mar-2008) use getVerticallyCenter() instead */ public boolean getVerticallyCenter(boolean value) { return getVerticallyCenter(); @@ -724,14 +659,19 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet } /** - * gets the region at a particular index - * @param index of the region to fetch - * @return the merged region (simple eh?) + * @deprecated (Aug-2008) use {@link HSSFSheet#getMergedRegion(int)} */ - - public Region getMergedRegionAt(int index) - { - return new Region(sheet.getMergedRegionAt(index)); + public Region getMergedRegionAt(int index) { + CellRangeAddress cra = getMergedRegion(index); + + return new Region(cra.getFirstRow(), (short)cra.getFirstColumn(), + cra.getLastRow(), (short)cra.getLastColumn()); + } + /** + * @return the merged region at the specified index + */ + public CellRangeAddress getMergedRegion(int index) { + return (CellRangeAddress)sheet.getMergedRegionAt(index); } /** @@ -1164,36 +1104,43 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet protected void shiftMerged(int startRow, int endRow, int n, boolean isRow) { List shiftedRegions = new ArrayList(); //move merged regions completely if they fall within the new region boundaries when they are shifted - for (int i = 0; i < this.getNumMergedRegions(); i++) { - Region merged = this.getMergedRegionAt(i); + for (int i = 0; i < getNumMergedRegions(); i++) { + CellRangeAddress merged = getMergedRegion(i); - boolean inStart = (merged.getRowFrom() >= startRow || merged.getRowTo() >= startRow); - boolean inEnd = (merged.getRowTo() <= endRow || merged.getRowFrom() <= endRow); + boolean inStart= (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow); + boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow); - //dont check if it's not within the shifted area - if (! (inStart && inEnd)) continue; + //don't check if it's not within the shifted area + if (!inStart || !inEnd) { + continue; + } //only shift if the region outside the shifted rows is not merged too - if (!merged.contains(startRow-1, (short)0) && !merged.contains(endRow+1, (short)0)){ - merged.setRowFrom(merged.getRowFrom()+n); - merged.setRowTo(merged.getRowTo()+n); + if (!containsCell(merged, startRow-1, 0) && !containsCell(merged, endRow+1, 0)){ + merged.setFirstRow(merged.getFirstRow()+n); + merged.setLastRow(merged.getLastRow()+n); //have to remove/add it back shiftedRegions.add(merged); - this.removeMergedRegion(i); + removeMergedRegion(i); i = i -1; // we have to back up now since we removed one - } - } - //readd so it doesn't get shifted again + //read so it doesn't get shifted again Iterator iterator = shiftedRegions.iterator(); while (iterator.hasNext()) { - Region region = (Region)iterator.next(); + CellRangeAddress region = (CellRangeAddress)iterator.next(); this.addMergedRegion(region); } - + } + private static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) { + if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx + && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx) + { + return true; + } + return false; } /** @@ -1812,17 +1759,20 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet HSSFRow row = (HSSFRow) it.next(); HSSFCell cell = row.getCell(column); - if (cell == null) continue; + if (cell == null) { + continue; + } int colspan = 1; for (int i = 0 ; i < getNumMergedRegions(); i++) { - if (getMergedRegionAt(i).contains(row.getRowNum(), column)) { + CellRangeAddress region = getMergedRegion(i); + if (containsCell(region, row.getRowNum(), column)) { if (!useMergedCells) { // If we're not using merged cells, skip this one and move on to the next. continue rows; } - cell = row.getCell(getMergedRegionAt(i).getColumnFrom()); - colspan = 1+ getMergedRegionAt(i).getColumnTo() - getMergedRegionAt(i).getColumnFrom(); + cell = row.getCell(region.getFirstColumn()); + colspan = 1 + region.getLastColumn() - region.getFirstColumn(); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java index 8e8cf40a1..cdfae6cd1 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java @@ -21,6 +21,7 @@ import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; import org.apache.poi.ss.util.Region; +import org.apache.poi.ss.util.CellRangeAddress; /** * The 'Conditional Formatting' facet of HSSFSheet @@ -100,7 +101,12 @@ public final class HSSFSheetConditionalFormatting { return _sheet.addConditionalFormatting(cfraClone); } - + /** + * @deprecated use CellRangeAddress instead of Region + */ + public int addConditionalFormatting(Region[] regions, HSSFConditionalFormattingRule[] cfRules) { + return addConditionalFormatting(Region.convertRegionsToCellRanges(regions), cfRules); + } /** * Allows to add a new Conditional Formatting set to the sheet. * @@ -109,8 +115,7 @@ public final class HSSFSheetConditionalFormatting { * * @return index of the newly created Conditional Formatting object */ - - public int addConditionalFormatting(Region[] regions, HSSFConditionalFormattingRule[] cfRules) { + public int addConditionalFormatting(CellRangeAddress[] regions, HSSFConditionalFormattingRule[] cfRules) { if (regions == null) { throw new IllegalArgumentException("regions must not be null"); } @@ -132,7 +137,7 @@ public final class HSSFSheetConditionalFormatting { return _sheet.addConditionalFormatting(cfra); } - public int addConditionalFormatting(Region[] regions, + public int addConditionalFormatting(CellRangeAddress[] regions, HSSFConditionalFormattingRule rule1) { return addConditionalFormatting(regions, @@ -142,7 +147,7 @@ public final class HSSFSheetConditionalFormatting { }); } - public int addConditionalFormatting(Region[] regions, + public int addConditionalFormatting(CellRangeAddress[] regions, HSSFConditionalFormattingRule rule1, HSSFConditionalFormattingRule rule2) { @@ -153,18 +158,6 @@ public final class HSSFSheetConditionalFormatting { }); } - public int addConditionalFormatting(Region[] regions, - HSSFConditionalFormattingRule rule1, - HSSFConditionalFormattingRule rule2, - HSSFConditionalFormattingRule rule3) - { - return addConditionalFormatting(regions, - new HSSFConditionalFormattingRule[] - { - rule1, rule2, rule3 - }); - } - /** * gets Conditional Formatting object at a particular index * diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddress.java b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java new file mode 100644 index 000000000..c7ff2653c --- /dev/null +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java @@ -0,0 +1,46 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.util; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.record.SelectionRecord; +import org.apache.poi.util.LittleEndian; + +/** + * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'

    + * + * Note - {@link SelectionRecord} uses the BIFF5 version of this structure + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + */ +public class CellRangeAddress extends org.apache.poi.ss.util.CellRangeAddress { + public CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol) { + super(firstRow, lastRow, firstCol, lastCol); + } + public CellRangeAddress() { + super(); + } + public CellRangeAddress(RecordInputStream in) { + if (in.remaining() < ENCODED_SIZE) { + // Ran out of data + throw new RuntimeException("Ran out of data reading CellRangeAddress"); + } + _firstRow = in.readUShort(); + _lastRow = in.readUShort(); + _firstCol = in.readUShort(); + _lastCol = in.readUShort(); + } +} diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java new file mode 100644 index 000000000..79ea50abb --- /dev/null +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java @@ -0,0 +1,57 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.util.LittleEndian; + +/** + * Implementation of the cell range address lists,like is described + * in OpenOffice.org's Excel Documentation: excelfileformat.pdf sec 2.5.14 - + * 'Cell Range Address List' + * + * In BIFF8 there is a common way to store absolute cell range address lists in + * several records (not formulas). A cell range address list consists of a field + * with the number of ranges and the list of the range addresses. Each cell + * range address (called an ADDR structure) contains 4 16-bit-values. + *

    + * + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + */ +public class CellRangeAddressList extends org.apache.poi.ss.util.CellRangeAddressList { + public CellRangeAddressList(int firstRow, int lastRow, int firstCol, int lastCol) { + super(firstRow,lastRow,firstCol,lastCol); + } + public CellRangeAddressList() { + super(); + } + + /** + * @param in the RecordInputstream to read the record from + */ + public CellRangeAddressList(RecordInputStream in) { + super(); + int nItems = in.readUShort(); + + for (int k = 0; k < nItems; k++) { + _list.add(new CellRangeAddress(in)); + } + } +} diff --git a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java b/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java deleted file mode 100644 index 73804966f..000000000 --- a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java +++ /dev/null @@ -1,269 +0,0 @@ -/* ==================================================================== - Copyright 2002-2004 Apache Software Foundation - - Licensed 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.util; - -import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.POILogFactory; -import org.apache.poi.util.POILogger; - -import java.util.ArrayList; - -/** - *

    Title: HSSFCellRangeAddress

    - *

    Description: - * Implementation of the cell range address lists,like is described in - * OpenOffice.org's Excel Documentation . - * In BIFF8 there is a common way to store absolute cell range address - * lists in several records (not formulas). A cell range address list - * consists of a field with the number of ranges and the list of the range - * addresses. Each cell range address (called an ADDR structure) contains - * 4 16-bit-values.

    - *

    Copyright: Copyright (c) 2004

    - *

    Company:

    - * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) - * @version 2.0-pre - */ - -public class HSSFCellRangeAddress -{ - private static POILogger logger = POILogFactory.getLogger(HSSFCellRangeAddress.class); - - /** - * Number of following ADDR structures - */ - private short field_addr_number; - - /** - * List of ADDR structures. Each structure represents a cell range - */ - private ArrayList field_regions_list; - - public HSSFCellRangeAddress() - { - - } - - /** - * Construct a new HSSFCellRangeAddress object and sets its fields appropriately . - * Even this isn't an Excel record , I kept the same behavior for reading/writing - * the object's data as for a regular record . - * - * @param in the RecordInputstream to read the record from - */ - public HSSFCellRangeAddress(RecordInputStream in) - { - this.fillFields(in); - } - - public void fillFields(RecordInputStream in) - { - this.field_addr_number = in.readShort(); - this.field_regions_list = new ArrayList(this.field_addr_number); - - for (int k = 0; k < this.field_addr_number; k++) - { - short first_row = in.readShort(); - short first_col = in.readShort(); - - short last_row = first_row; - short last_col = first_col; - if(in.remaining() >= 4) { - last_row = in.readShort(); - last_col = in.readShort(); - } else { - // Ran out of data - // For now, issue a warning, finish, and - // hope for the best.... - logger.log(POILogger.WARN, "Ran out of data reading cell references for DVRecord"); - k = this.field_addr_number; - } - - AddrStructure region = new AddrStructure(first_row, first_col, last_row, last_col); - this.field_regions_list.add(region); - } - } - - /** - * Get the number of following ADDR structures. - * The number of this structures is automatically set when reading an Excel file - * and/or increased when you manually add a new ADDR structure . - * This is the reason there isn't a set method for this field . - * @return number of ADDR structures - */ - public short getADDRStructureNumber() - { - return this.field_addr_number; - } - - /** - * Add an ADDR structure . - * @param first_row - the upper left hand corner's row - * @param first_col - the upper left hand corner's col - * @param last_row - the lower right hand corner's row - * @param last_col - the lower right hand corner's col - * @return the index of this ADDR structure - */ - public int addADDRStructure(short first_row, short first_col, short last_row, short last_col) - { - if (this.field_regions_list == null) - { - //just to be sure :-) - this.field_addr_number= 0; - this.field_regions_list = new ArrayList(10); - } - AddrStructure region = new AddrStructure(first_row, last_row, first_col, last_col); - - this.field_regions_list.add(region); - this.field_addr_number++; - return this.field_addr_number; - } - - /** - * Remove the ADDR structure stored at the passed in index - * @param index The ADDR structure's index - */ - public void removeADDRStructureAt(int index) - { - this.field_regions_list.remove(index); - this.field_addr_number--; - } - - /** - * return the ADDR structure at the given index. - * @return AddrStructure representing - */ - public AddrStructure getADDRStructureAt(int index) - { - return ( AddrStructure ) this.field_regions_list.get(index); - } - - public int serialize(int offset, byte [] data) - { - int pos = 2; - - LittleEndian.putShort(data, offset, this.getADDRStructureNumber()); - for (int k = 0; k < this.getADDRStructureNumber(); k++) - { - AddrStructure region = this.getADDRStructureAt(k); - LittleEndian.putShort(data, offset + pos, region.getFirstRow()); - pos += 2; - LittleEndian.putShort(data, offset + pos, region.getLastRow()); - pos += 2; - LittleEndian.putShort(data, offset + pos, region.getFirstColumn()); - pos += 2; - LittleEndian.putShort(data, offset + pos, region.getLastColumn()); - pos += 2; - } - return this.getSize(); - } - - public int getSize() - { - return 2 + this.field_addr_number*8; - } - - public class AddrStructure - { - private short _first_row; - private short _first_col; - private short _last_row; - private short _last_col; - - public AddrStructure(short first_row, short last_row, short first_col, short last_col) - { - this._first_row = first_row; - this._last_row = last_row; - this._first_col = first_col; - this._last_col = last_col; - } - - /** - * get the upper left hand corner column number - * @return column number for the upper left hand corner - */ - public short getFirstColumn() - { - return this._first_col; - } - - /** - * get the upper left hand corner row number - * @return row number for the upper left hand corner - */ - public short getFirstRow() - { - return this._first_row; - } - - /** - * get the lower right hand corner column number - * @return column number for the lower right hand corner - */ - public short getLastColumn() - { - return this._last_col; - } - - /** - * get the lower right hand corner row number - * @return row number for the lower right hand corner - */ - public short getLastRow() - { - return this._last_row; - } - - /** - * set the upper left hand corner column number - * @param this._first_col column number for the upper left hand corner - */ - public void setFirstColumn(short first_col) - { - this._first_col = first_col; - } - - /** - * set the upper left hand corner row number - * @param rowFrom row number for the upper left hand corner - */ - public void setFirstRow(short first_row) - { - this._first_row = first_row; - } - - /** - * set the lower right hand corner column number - * @param colTo column number for the lower right hand corner - */ - public void setLastColumn(short last_col) - { - this._last_col = last_col; - } - - /** - * get the lower right hand corner row number - * @param rowTo row number for the lower right hand corner - */ - public void setLastRow(short last_row) - { - this._last_row = last_row; - } - } -} - - diff --git a/src/java/org/apache/poi/hssf/util/HSSFDataValidation.java b/src/java/org/apache/poi/hssf/util/HSSFDataValidation.java deleted file mode 100644 index af578bee4..000000000 --- a/src/java/org/apache/poi/hssf/util/HSSFDataValidation.java +++ /dev/null @@ -1,471 +0,0 @@ -/* ==================================================================== - Copyright 2002-2004 Apache Software Foundation - - Licensed 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.util; - -/** - *

    Title: HSSFDataValidation

    - *

    Description: Utilty class for creating data validation cells

    - *

    Copyright: Copyright (c) 2004

    - *

    Company:

    - * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) - * @version 2.0-pre - */ - -public class HSSFDataValidation -{ - /** - * Validation data type constants - */ - /** - * Any type - */ - public static final int DATA_TYPE_ANY = 0x00; - /** - * Integer type - */ - public static final int DATA_TYPE_INTEGER = 0x01; - /** - * Decimal type - */ - public static final int DATA_TYPE_DECIMAL = 0x02; - /** - * List type ( combo box type ) - */ - public static final int DATA_TYPE_LIST = 0x03; - /** - * Date type - */ - public static final int DATA_TYPE_DATE = 0x04; - /** - * Time type - */ - public static final int DATA_TYPE_TIME = 0x05; - /** - * String length type - */ - public static final int DATA_TYPE_TEXT_LENGTH = 0x06; - /** - * Formula ( custom ) type - */ - public static final int DATA_TYPE_FORMULA = 0x07; - - /** - * Error style constants for error box - */ - /** - * STOP style like - */ - public static final int ERROR_STYLE_STOP = 0x00; - /** - * WARNING style like - */ - public static final int ERROR_STYLE_WARNING = 0x01; - /** - * INFO style like - */ - public static final int ERROR_STYLE_INFO = 0x02; - - /** - * Condition operator - */ - public static final int OPERATOR_BETWEEN = 0x00; - public static final int OPERATOR_NOT_BETWEEN = 0x01; - public static final int OPERATOR_EQUAL = 0x02; - public static final int OPERATOR_NOT_EQUAL = 0x03; - public static final int OPERATOR_GREATER_THAN = 0x04; - public static final int OPERATOR_LESS_THAN = 0x05; - public static final int OPERATOR_GREATER_OR_EQUAL = 0x06; - public static final int OPERATOR_LESS_OR_EQUAL = 0x07; - - private short _first_row = 0; - private short _first_col = 0; - private short _last_row = 0; - private short _last_col = 0; - - private String _prompt_title = null; - private String _prompt_text = null; - private String _error_title = null; - private String _error_text = null; - private String _string_first_formula = null; - private String _string_sec_formula = null; - - private int _data_type = HSSFDataValidation.DATA_TYPE_ANY; - private int _error_style = HSSFDataValidation.ERROR_STYLE_STOP; - private boolean _list_explicit_formula = true; - private boolean _empty_cell_allowed = true; - private boolean _surpress_dropdown_arrow = false; - private boolean _show_prompt_box = true; - private boolean _show_error_box = true; - private int _operator = HSSFDataValidation.OPERATOR_BETWEEN; - - - /** - * Empty constructor - */ - public HSSFDataValidation( ) - { - } - - /** - * Constructor wich initializes the cell range on wich this object will be applied - * @param first_row First row - * @param first_col First column - * @param last_row Last row - * @param last_col Last column - */ - public HSSFDataValidation( short first_row, short first_col, short last_row, short last_col ) - { - this._first_row = first_row; - this._first_col = first_col; - this._last_row = last_row; - this._last_col = last_col; - } - - /** - * Set the type of this object - * @param data_type The type - * @see DATA_TYPE_ANY, DATA_TYPE_INTEGER, DATA_TYPE_DECIMNAL, DATA_TYPE_LIST, DATA_TYPE_DATE, - * DATA_TYPE_TIME, DATA_TYPE_TEXT_LENTGH, DATA_TYPE_FORMULA - */ - public void setDataValidationType( int data_type ) - { - this._data_type = data_type; - } - - /** - * The data type of this object - * @return The type - * @see DATA_TYPE_ANY, DATA_TYPE_INTEGER, DATA_TYPE_DECIMNAL, DATA_TYPE_LIST, DATA_TYPE_DATE, - * DATA_TYPE_TIME, DATA_TYPE_TEXT_LENTGH, DATA_TYPE_FORMULA - */ - public int getDataValidationType() - { - return this._data_type; - } - - /** - * Sets the error style for error box - * @param error_style Error style constant - * @see ERROR_STYLE_STOP, ERROR_STYLE_WARNING, ERROR_STYLE_INFO - */ - public void setErrorStyle( int error_style ) - { - this._error_style = error_style; - } - - /** - * returns the error style of errror box - * @return the style constant - * @see ERROR_STYLE_STOP, ERROR_STYLE_WARNING, ERROR_STYLE_INFO - */ - public int getErrorStyle( ) - { - return this._error_style; - } - - /** - * If this object has an explicit formula . This is useful only for list data validation object - * @param explicit True if use an explicit formula - */ - public void setExplicitListFormula( boolean explicit ) - { - this._list_explicit_formula = explicit; - } - - /** - * Returns the settings for explicit formula . This is useful only for list data validation objects. - * This method always returns false if the object isn't a list validation object - * @see setDataValidationType( int data_type ) - * @return - */ - public boolean getExplicitListFormula( ) - { - if ( this._data_type != HSSFDataValidation.DATA_TYPE_LIST ) - { - return false; - } - return this._list_explicit_formula ; - } - - /** - * Sets if this object allows empty as a valid value - * @param allowed True if this object should treats empty as valid value , false otherwise - */ - public void setEmptyCellAllowed( boolean allowed ) - { - this._empty_cell_allowed = allowed; - } - - /** - * Retrieve the settings for empty cells allowed - * @return True if this object should treats empty as valid value , false otherwise - */ - public boolean getEmptyCellAllowed( ) - { - return this._empty_cell_allowed ; - } - - /** - * Useful for list validation objects . - * @param surppres True if a list should display the values into a drop down list , false otherwise . - * In other words , if a list should display the arrow sign on its right side - */ - public void setSurppressDropDownArrow( boolean surppres ) - { - this._surpress_dropdown_arrow = surppres; - } - - /** - * Useful only list validation objects . - * This method always returns false if the object isn't a list validation object - * @return True if a list should display the values into a drop down list , false otherwise . - * @see setDataValidationType( int data_type ) - */ - public boolean getSurppressDropDownArrow( ) - { - if ( this._data_type != HSSFDataValidation.DATA_TYPE_LIST ) - { - return false; - } - return this._surpress_dropdown_arrow ; - } - - /** - * Sets the behaviour when a cell which belongs to this object is selected - * @param show True if an prompt box should be displayed , false otherwise - */ - public void setShowPromptBox( boolean show ) - { - this._show_prompt_box = show; - } - - /** - * @param show True if an prompt box should be displayed , false otherwise - */ - public boolean getShowPromptBox( ) - { - if ( (this.getPromptBoxText() == null) && (this.getPromptBoxTitle() == null) ) - { - return false; - } - return this._show_prompt_box ; - } - - /** - * Sets the behaviour when an invalid value is entered - * @param show True if an error box should be displayed , false otherwise - */ - public void setShowErrorBox( boolean show ) - { - this._show_error_box = show; - } - - /** - * @return True if an error box should be displayed , false otherwise - */ - public boolean getShowErrorBox( ) - { - if ( (this.getErrorBoxText() == null) && (this.getErrorBoxTitle() == null) ) - { - return false; - } - return this._show_error_box ; - } - - /** - * Sets the operator involved in the formula whic governs this object - * Example : if you wants that a cell to accept only values between 1 and 5 , which - * mathematically means 1 <= value <= 5 , then the operator should be OPERATOR_BETWEEN - * @param operator A constant for operator - * @see OPERATOR_BETWEEN, OPERATOR_NOT_BETWEEN, OPERATOR_EQUAL, OPERATOR_NOT_EQUAL - * OPERATOR_GREATER_THAN, OPERATOR_LESS_THAN, OPERATOR_GREATER_OR_EQUAL, - * OPERATOR_LESS_OR_EQUAL - */ - public void setOperator( int operator ) - { - this._operator = operator; - } - - /** - * Retrieves the operator used for this object's formula - * @return - * @see OPERATOR_BETWEEN, OPERATOR_NOT_BETWEEN, OPERATOR_EQUAL, OPERATOR_NOT_EQUAL - * OPERATOR_GREATER_THAN, OPERATOR_LESS_THAN, OPERATOR_GREATER_OR_EQUAL, - * OPERATOR_LESS_OR_EQUAL - */ - public int getOperator() - { - return this._operator; - } - - /** - * Sets the title and text for the prompt box . Prompt box is displayed when the user - * selects a cell which belongs to this validation object . In order for a prompt box - * to be displayed you should also use method setShowPromptBox( boolean show ) - * @param title The prompt box's title - * @param text The prompt box's text - * @see setShowPromptBox( boolean show ) - */ - public void createPromptBox( String title, String text ) - { - this._prompt_title = title; - this._prompt_text = text; - this.setShowPromptBox(true); - } - - /** - * Returns the prompt box's title - * @return Prompt box's title or null - */ - public String getPromptBoxTitle( ) - { - return this._prompt_title; - } - - /** - * Returns the prompt box's text - * @return Prompt box's text or null - */ - public String getPromptBoxText( ) - { - return this._prompt_text; - } - - /** - * Sets the title and text for the error box . Error box is displayed when the user - * enters an invalid value int o a cell which belongs to this validation object . - * In order for an error box to be displayed you should also use method - * setShowErrorBox( boolean show ) - * @param title The error box's title - * @param text The error box's text - * @see setShowErrorBox( boolean show ) - */ - public void createErrorBox( String title, String text ) - { - this._error_title = title; - this._error_text = text; - this.setShowErrorBox(true); - } - - /** - * Returns the error box's title - * @return Error box's title or null - */ - public String getErrorBoxTitle( ) - { - return this._error_title; - } - - /** - * Returns the error box's text - * @return Error box's text or null - */ - public String getErrorBoxText( ) - { - return this._error_text; - } - - /** - * Sets the first formula for this object . - * A formula is divided into three parts : first formula , operator and second formula . - * In other words , a formula contains a left oprand , an operator and a right operand. - * This is the general rule . An example is 1<= value <= 5 . In this case , - * the left operand ( or the first formula ) is the number 1 . The operator is - * OPERATOR_BETWEEN and the right operand ( or the second formula ) is 5 . - * @param formula - */ - public void setFirstFormula( String formula ) - { - this._string_first_formula = formula; - } - - /** - * Returns the first formula - * @return - */ - public String getFirstFormula( ) - { - return this._string_first_formula; - } - - /** - * Sets the first formula for this object . - * A formula is divided into three parts : first formula , operator and second formula . - * In other words , a formula contains a left oprand , an operator and a right operand. - * This is the general rule . An example is 1<= value <=5 . In this case , - * the left operand ( or the first formula ) is the number 1 . The operator is - * OPERATOR_BETWEEN and the right operand ( or the second formula ) is 5 . - * But there are cases when a second formula isn't needed : - * You want somethink like : all values less than 5 . In this case , there's only a first - * formula ( in our case 5 ) and the operator OPERATOR_LESS_THAN - * @param formula - */ - public void setSecondFormula( String formula ) - { - this._string_sec_formula = formula; - } - - /** - * Returns the second formula - * @return - */ - public String getSecondFormula( ) - { - return this._string_sec_formula; - } - - public void setFirstRow( short first_row ) - { - this._first_row = first_row; - } - - public void setFirstColumn( short first_column ) - { - this._first_col = first_column; - } - - public void setLastRow( short last_row ) - { - this._last_row = last_row; - } - - public void setLastColumn( short last_column ) - { - this._last_col = last_column; - } - - public short getFirstRow() - { - return this._first_row; - } - - public short getFirstColumn() - { - return this._first_col; - } - - public short getLastRow() - { - return this._last_row; - } - - public short getLastColumn() - { - return this._last_col; - } - -} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/util/Region.java b/src/java/org/apache/poi/hssf/util/Region.java index 3f634af29..25a67db21 100644 --- a/src/java/org/apache/poi/hssf/util/Region.java +++ b/src/java/org/apache/poi/hssf/util/Region.java @@ -18,8 +18,6 @@ package org.apache.poi.hssf.util; -import org.apache.poi.hssf.record.MergeCellsRecord.MergedRegion; - /** * Represents a from/to row/col square. This is a object primitive * that can be used to represent row,col - row,col just as one would use String @@ -43,16 +41,6 @@ public class Region extends org.apache.poi.ss.util.Region super(rowFrom, colFrom, rowTo, colTo); } - /** - * special constructor (I know this is bad but it is so wrong that its right - * okay) that makes a region from a mergedcells's region subrecord. - */ - - public Region(MergedRegion region) - { - super(region); - } - public Region(String ref) { super(ref); } diff --git a/src/java/org/apache/poi/ss/usermodel/DateUtil.java b/src/java/org/apache/poi/ss/usermodel/DateUtil.java index 08f8dda43..002b4aedc 100644 --- a/src/java/org/apache/poi/ss/usermodel/DateUtil.java +++ b/src/java/org/apache/poi/ss/usermodel/DateUtil.java @@ -16,17 +16,12 @@ ==================================================================== */ - -/* - * DateUtil.java - * - * Created on January 19, 2002, 9:30 AM - */ package org.apache.poi.ss.usermodel; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.regex.Pattern; /** * Contains methods for dealing with Excel dates. @@ -38,17 +33,20 @@ import java.util.GregorianCalendar; * @author Alex Jacoby (ajacoby at gmail.com) * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) */ - -public class DateUtil -{ - protected DateUtil() - { +public class DateUtil { + protected DateUtil() { + // no instances of this class } + private static final int SECONDS_PER_MINUTE = 60; + private static final int MINUTES_PER_HOUR = 60; + private static final int HOURS_PER_DAY = 24; + private static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + private static final int BAD_DATE = -1; // used to specify that date is invalid + private static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L; + + private static final Pattern TIME_SEPARATOR_PATTERN = Pattern.compile(":"); - private static final int BAD_DATE = - -1; // used to specify that date is invalid - private static final long DAY_MILLISECONDS = 24 * 60 * 60 * 1000; - /** * Given a Date, converts it into a double representing its internal Excel representation, * which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds. @@ -57,7 +55,7 @@ public class DateUtil * @param date the Date */ public static double getExcelDate(Date date) { - return getExcelDate(date, false); + return getExcelDate(date, false); } /** * Given a Date, converts it into a double representing its internal Excel representation, @@ -74,8 +72,8 @@ public class DateUtil } /** * Given a Date in the form of a Calendar, converts it into a double - * representing its internal Excel representation, which is the - * number of days since 1/1/1900. Fractional days represent hours, + * representing its internal Excel representation, which is the + * number of days since 1/1/1900. Fractional days represent hours, * minutes, and seconds. * * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1) @@ -83,41 +81,40 @@ public class DateUtil * @param use1904windowing Should 1900 or 1904 date windowing be used? */ public static double getExcelDate(Calendar date, boolean use1904windowing) { - // Don't alter the supplied Calendar as we do our work - return internalGetExcelDate( (Calendar)date.clone(), use1904windowing ); + // Don't alter the supplied Calendar as we do our work + return internalGetExcelDate( (Calendar)date.clone(), use1904windowing ); } private static double internalGetExcelDate(Calendar date, boolean use1904windowing) { - if ((!use1904windowing && date.get(Calendar.YEAR) < 1900) || - (use1904windowing && date.get(Calendar.YEAR) < 1904)) + if ((!use1904windowing && date.get(Calendar.YEAR) < 1900) || + (use1904windowing && date.get(Calendar.YEAR) < 1904)) { return BAD_DATE; - } else { - // Because of daylight time saving we cannot use - // date.getTime() - calStart.getTimeInMillis() - // as the difference in milliseconds between 00:00 and 04:00 - // can be 3, 4 or 5 hours but Excel expects it to always - // be 4 hours. - // E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours - // and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours - double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 - + date.get(Calendar.MINUTE) - ) * 60 + date.get(Calendar.SECOND) - ) * 1000 + date.get(Calendar.MILLISECOND) - ) / ( double ) DAY_MILLISECONDS; - Calendar calStart = dayStart(date); - - double value = fraction + absoluteDay(calStart, use1904windowing); - - if (!use1904windowing && value >= 60) { - value++; - } else if (use1904windowing) { - value--; - } - - return value; } + // Because of daylight time saving we cannot use + // date.getTime() - calStart.getTimeInMillis() + // as the difference in milliseconds between 00:00 and 04:00 + // can be 3, 4 or 5 hours but Excel expects it to always + // be 4 hours. + // E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours + // and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours + double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 + + date.get(Calendar.MINUTE) + ) * 60 + date.get(Calendar.SECOND) + ) * 1000 + date.get(Calendar.MILLISECOND) + ) / ( double ) DAY_MILLISECONDS; + Calendar calStart = dayStart(date); + + double value = fraction + absoluteDay(calStart, use1904windowing); + + if (!use1904windowing && value >= 60) { + value++; + } else if (use1904windowing) { + value--; + } + + return value; } - + /** * Given an Excel date with using 1900 date windowing, and * converts it to a java.util.Date. @@ -130,13 +127,13 @@ public class DateUtil * Europe/Copenhagen, on 2004-03-28 the minute after * 01:59 CET is 03:00 CEST, if the excel date represents a time between * 02:00 and 03:00 then it is converted to past 03:00 summer time - * + * * @param date The Excel date. * @return Java representation of the date, or null if date is not a valid Excel date * @see java.util.TimeZone */ public static Date getJavaDate(double date) { - return getJavaDate(date, false); + return getJavaDate(date, false); } /** * Given an Excel date with either 1900 or 1904 date windowing, @@ -158,95 +155,90 @@ public class DateUtil * @see java.util.TimeZone */ public static Date getJavaDate(double date, boolean use1904windowing) { - if (isValidExcelDate(date)) { - int startYear = 1900; - int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't - int wholeDays = (int)Math.floor(date); - if (use1904windowing) { - startYear = 1904; - dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day - } - else if (wholeDays < 61) { - // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists - // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation - dayAdjust = 0; - } - GregorianCalendar calendar = new GregorianCalendar(startYear,0, - wholeDays + dayAdjust); - int millisecondsInDay = (int)((date - Math.floor(date)) * - DAY_MILLISECONDS + 0.5); - calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay); - return calendar.getTime(); - } - else { + if (!isValidExcelDate(date)) { return null; } + int startYear = 1900; + int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't + int wholeDays = (int)Math.floor(date); + if (use1904windowing) { + startYear = 1904; + dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day + } + else if (wholeDays < 61) { + // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists + // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation + dayAdjust = 0; + } + GregorianCalendar calendar = new GregorianCalendar(startYear,0, + wholeDays + dayAdjust); + int millisecondsInDay = (int)((date - Math.floor(date)) * + DAY_MILLISECONDS + 0.5); + calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay); + return calendar.getTime(); } - + /** * Given a format ID and its format String, will check to see if the * format represents a date format or not. * Firstly, it will check to see if the format ID corresponds to an - * internal excel date format (eg most US date formats) + * internal excel date format (eg most US date formats) * If not, it will check to see if the format string only contains * date formatting characters (ymd-/), which covers most * non US date formats. - * + * * @param formatIndex The index of the format, eg from ExtendedFormatRecord.getFormatIndex * @param formatString The format string, eg from FormatRecord.getFormatString * @see #isInternalDateFormat(int) */ public static boolean isADateFormat(int formatIndex, String formatString) { - // First up, is this an internal date format? - if(isInternalDateFormat(formatIndex)) { - return true; - } - - // If we didn't get a real string, it can't be - if(formatString == null || formatString.length() == 0) { - return false; - } - - String fs = formatString; - - // Translate \- into just -, before matching - fs = fs.replaceAll("\\\\-","-"); - // And \, into , - fs = fs.replaceAll("\\\\,",","); - // And '\ ' into ' ' - fs = fs.replaceAll("\\\\ "," "); - - // If it end in ;@, that's some crazy dd/mm vs mm/dd - // switching stuff, which we can ignore - fs = fs.replaceAll(";@", ""); - - // If it starts with [$-...], then could be a date, but - // who knows what that starting bit is all about - fs = fs.replaceAll("^\\[\\$\\-.*?\\]", ""); - - // If it starts with something like [Black] or [Yellow], - // then it could be a date - fs = fs.replaceAll("^\\[[a-zA-Z]+\\]", ""); - - // Otherwise, check it's only made up, in any case, of: - // y m d h s - / , . : - // optionally followed by AM/PM - // optionally followed by AM/PM - if(fs.matches("^[yYmMdDhHsS\\-/,. :]+[ampAMP/]*$")) { - return true; - } - - return false; + // First up, is this an internal date format? + if(isInternalDateFormat(formatIndex)) { + return true; + } + + // If we didn't get a real string, it can't be + if(formatString == null || formatString.length() == 0) { + return false; + } + + String fs = formatString; + + // Translate \- into just -, before matching + fs = fs.replaceAll("\\\\-","-"); + // And \, into , + fs = fs.replaceAll("\\\\,",","); + // And '\ ' into ' ' + fs = fs.replaceAll("\\\\ "," "); + + // If it end in ;@, that's some crazy dd/mm vs mm/dd + // switching stuff, which we can ignore + fs = fs.replaceAll(";@", ""); + + // If it starts with [$-...], then could be a date, but + // who knows what that starting bit is all about + fs = fs.replaceAll("^\\[\\$\\-.*?\\]", ""); + + // If it starts with something like [Black] or [Yellow], + // then it could be a date + fs = fs.replaceAll("^\\[[a-zA-Z]+\\]", ""); + + // Otherwise, check it's only made up, in any case, of: + // y m d h s - / , . : + // optionally followed by AM/PM + if(fs.matches("^[yYmMdDhHsS\\-/,. :]+[ampAMP/]*$")) { + return true; + } + + return false; } /** * Given a format ID this will check whether the format represents * an internal excel date format or not. - * @see #isADateFormat(int, java.lang.String) + * @see #isADateFormat(int, java.lang.String) */ public static boolean isInternalDateFormat(int format) { - boolean retval =false; - switch(format) { // Internal Date Formats as described on page 427 in // Microsoft Excel Dev's Kit... @@ -262,32 +254,25 @@ public class DateUtil case 0x2d: case 0x2e: case 0x2f: - retval = true; - break; - - default: - retval = false; - break; + return true; } - return retval; + return false; } /** * Check if a cell contains a date - * Since dates are stored internally in Excel as double values - * we infer it is a date if it is formatted as such. + * Since dates are stored internally in Excel as double values + * we infer it is a date if it is formatted as such. * @see #isADateFormat(int, String) * @see #isInternalDateFormat(int) */ public static boolean isCellDateFormatted(Cell cell) { if (cell == null) return false; boolean bDate = false; - + double d = cell.getNumericCellValue(); if ( DateUtil.isValidExcelDate(d) ) { CellStyle style = cell.getCellStyle(); - if(style == null) return false; - int i = style.getDataFormat(); String f = style.getDataFormatString(); bDate = isADateFormat(i, f); @@ -305,7 +290,7 @@ public class DateUtil public static boolean isCellInternalDateFormatted(Cell cell) { if (cell == null) return false; boolean bDate = false; - + double d = cell.getNumericCellValue(); if ( DateUtil.isValidExcelDate(d) ) { CellStyle style = cell.getCellStyle(); @@ -335,7 +320,6 @@ public class DateUtil * @param cal the Calendar * @exception IllegalArgumentException if date is invalid */ - protected static int absoluteDay(Calendar cal, boolean use1904windowing) { return cal.get(Calendar.DAY_OF_YEAR) @@ -347,7 +331,7 @@ public class DateUtil * * @return days number of days in years prior to yr. * @param yr a year (1900 < yr < 4000) - * @param use1904windowing + * @param use1904windowing * @exception IllegalArgumentException if year is outside of range. */ @@ -356,16 +340,16 @@ public class DateUtil if ((!use1904windowing && yr < 1900) || (use1904windowing && yr < 1900)) { throw new IllegalArgumentException("'year' must be 1900 or greater"); } - + int yr1 = yr - 1; int leapDays = yr1 / 4 // plus julian leap days in prior years - yr1 / 100 // minus prior century years - + yr1 / 400 // plus years divisible by 400 + + yr1 / 400 // plus years divisible by 400 - 460; // leap days in previous 1900 years - + return 365 * (yr - (use1904windowing ? 1904 : 1900)) + leapDays; } - + // set HH:MM:SS fields of cal to 00:00:00:000 private static Calendar dayStart(final Calendar cal) { @@ -380,5 +364,95 @@ public class DateUtil return cal; } - // --------------------------------------------------------------------------------------------------------- + + private static final class FormatException extends Exception { + public FormatException(String msg) { + super(msg); + } + } + + /** + * Converts a string of format "HH:MM" or "HH:MM:SS" to its (Excel) numeric equivalent + * + * @return a double between 0 and 1 representing the fraction of the day + */ + public static double convertTime(String timeStr) { + try { + return convertTimeInternal(timeStr); + } catch (FormatException e) { + String msg = "Bad time format '" + timeStr + + "' expected 'HH:MM' or 'HH:MM:SS' - " + e.getMessage(); + throw new IllegalArgumentException(msg); + } + } + private static double convertTimeInternal(String timeStr) throws FormatException { + int len = timeStr.length(); + if (len < 4 || len > 8) { + throw new FormatException("Bad length"); + } + String[] parts = TIME_SEPARATOR_PATTERN.split(timeStr); + + String secStr; + switch (parts.length) { + case 2: secStr = "00"; break; + case 3: secStr = parts[2]; break; + default: + throw new FormatException("Expected 2 or 3 fields but got (" + parts.length + ")"); + } + String hourStr = parts[0]; + String minStr = parts[1]; + int hours = parseInt(hourStr, "hour", HOURS_PER_DAY); + int minutes = parseInt(minStr, "minute", MINUTES_PER_HOUR); + int seconds = parseInt(secStr, "second", SECONDS_PER_MINUTE); + + double totalSeconds = seconds + (minutes + (hours) * 60) * 60; + return totalSeconds / (SECONDS_PER_DAY); + } + /** + * Converts a string of format "YYYY/MM/DD" to its (Excel) numeric equivalent + * + * @return a double representing the (integer) number of days since the start of the Excel epoch + */ + public static Date parseYYYYMMDDDate(String dateStr) { + try { + return parseYYYYMMDDDateInternal(dateStr); + } catch (FormatException e) { + String msg = "Bad time format " + dateStr + + " expected 'YYYY/MM/DD' - " + e.getMessage(); + throw new IllegalArgumentException(msg); + } + } + private static Date parseYYYYMMDDDateInternal(String timeStr) throws FormatException { + if(timeStr.length() != 10) { + throw new FormatException("Bad length"); + } + + String yearStr = timeStr.substring(0, 4); + String monthStr = timeStr.substring(5, 7); + String dayStr = timeStr.substring(8, 10); + int year = parseInt(yearStr, "year", Short.MIN_VALUE, Short.MAX_VALUE); + int month = parseInt(monthStr, "month", 1, 12); + int day = parseInt(dayStr, "day", 1, 31); + + Calendar cal = new GregorianCalendar(year, month-1, day, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + private static int parseInt(String strVal, String fieldName, int rangeMax) throws FormatException { + return parseInt(strVal, fieldName, 0, rangeMax-1); + } + + private static int parseInt(String strVal, String fieldName, int lowerLimit, int upperLimit) throws FormatException { + int result; + try { + result = Integer.parseInt(strVal); + } catch (NumberFormatException e) { + throw new FormatException("Bad int format '" + strVal + "' for " + fieldName + " field"); + } + if (result < lowerLimit || result > upperLimit) { + throw new FormatException(fieldName + " value (" + result + + ") is outside the allowable range(0.." + upperLimit + ")"); + } + return result; + } } diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddress.java b/src/java/org/apache/poi/ss/util/CellRangeAddress.java new file mode 100644 index 000000000..e6534b34e --- /dev/null +++ b/src/java/org/apache/poi/ss/util/CellRangeAddress.java @@ -0,0 +1,167 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.util; + +import org.apache.poi.hssf.record.SelectionRecord; +import org.apache.poi.util.LittleEndian; + +/** + * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'

    + * + * Note - {@link SelectionRecord} uses the BIFF5 version of this structure + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + */ +public class CellRangeAddress { + /* + * TODO - replace org.apache.poi.hssf.util.Region + */ + public static final int ENCODED_SIZE = 8; + + /** max 65536 rows in BIFF8 */ + public static final int LAST_ROW_INDEX = 0x00FFFF; + /** max 256 columns in BIFF8 */ + public static final int LAST_COLUMN_INDEX = 0x00FF; + + + protected int _firstRow; + protected int _firstCol; + protected int _lastRow; + protected int _lastCol; + + protected CellRangeAddress() {} + public CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol) { + if(!isValid(firstRow, lastRow, firstCol, lastCol)) { + throw new IllegalArgumentException("invalid cell range (" + firstRow + ", " + lastRow + + ", " + firstCol + ", " + lastCol + ")"); + } + _firstRow = firstRow; + _lastRow = convertM1ToMax(lastRow, LAST_ROW_INDEX); + _firstCol = firstCol; + _lastCol = convertM1ToMax(lastCol, LAST_COLUMN_INDEX); + } + + private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn) + { + if(lastRow < 0 || lastRow > LAST_ROW_INDEX) { + return false; + } + if(firstRow < 0 || firstRow > LAST_ROW_INDEX) { + return false; + } + + if(lastColumn < 0 || lastColumn > LAST_COLUMN_INDEX) { + return false; + } + if(firstColumn < 0 || firstColumn > LAST_COLUMN_INDEX) { + return false; + } + return true; + } + /** + * Range arithmetic is easier when using a large positive number for 'max row or column' + * instead of -1. + */ + private static int convertM1ToMax(int lastIx, int maxIndex) { + if(lastIx < 0) { + return maxIndex; + } + return lastIx; + } + + public boolean isFullColumnRange() { + return _firstRow == 0 && _lastRow == LAST_ROW_INDEX; + } + public boolean isFullRowRange() { + return _firstCol == 0 && _lastCol == LAST_COLUMN_INDEX; + } + + /** + * @return column number for the upper left hand corner + */ + public int getFirstColumn() { + return _firstCol; + } + + /** + * @return row number for the upper left hand corner + */ + public int getFirstRow() { + return _firstRow; + } + + /** + * @return column number for the lower right hand corner + */ + public int getLastColumn() { + return _lastCol; + } + + /** + * @return row number for the lower right hand corner + */ + public int getLastRow() { + return _lastRow; + } + + /** + * @param _firstCol column number for the upper left hand corner + */ + public void setFirstColumn(int firstCol) { + _firstCol = firstCol; + } + + /** + * @param rowFrom row number for the upper left hand corner + */ + public void setFirstRow(int firstRow) { + _firstRow = firstRow; + } + + /** + * @param colTo column number for the lower right hand corner + */ + public void setLastColumn(int lastCol) { + _lastCol = lastCol; + } + + /** + * @param rowTo row number for the lower right hand corner + */ + public void setLastRow(int lastRow) { + _lastRow = lastRow; + } + + public CellRangeAddress copy() { + return new CellRangeAddress(_firstRow, _lastRow, _firstCol, _lastCol); + } + + public static int getEncodedSize(int numberOfItems) { + return numberOfItems * ENCODED_SIZE; + } + + public String toString() { + return getClass().getName() + " ["+_firstRow+", "+_lastRow+", "+_firstCol+", "+_lastCol+"]"; + } + + public int serialize(int offset, byte[] data) { + LittleEndian.putUShort(data, offset + 0, _firstRow); + LittleEndian.putUShort(data, offset + 2, _lastRow); + LittleEndian.putUShort(data, offset + 4, _firstCol); + LittleEndian.putUShort(data, offset + 6, _lastCol); + return ENCODED_SIZE; + } +} diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressList.java b/src/java/org/apache/poi/ss/util/CellRangeAddressList.java new file mode 100644 index 000000000..8773c34ee --- /dev/null +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressList.java @@ -0,0 +1,132 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.util; + +import java.util.ArrayList; +import java.util.List; +import org.apache.poi.util.LittleEndian; + +/** + * Implementation of the cell range address lists,like is described + * in OpenOffice.org's Excel Documentation: excelfileformat.pdf sec 2.5.14 - + * 'Cell Range Address List' + * + * In BIFF8 there is a common way to store absolute cell range address lists in + * several records (not formulas). A cell range address list consists of a field + * with the number of ranges and the list of the range addresses. Each cell + * range address (called an ADDR structure) contains 4 16-bit-values. + *

    + * + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + */ +public class CellRangeAddressList { + + /** + * List of CellRangeAddresses. Each structure represents a cell range + */ + protected final List _list; + + public CellRangeAddressList() { + _list = new ArrayList(); + } + /** + * Convenience constructor for creating a CellRangeAddressList with a single + * CellRangeAddress. Other CellRangeAddresses may be added later. + */ + public CellRangeAddressList(int firstRow, int lastRow, int firstCol, int lastCol) { + this(); + addCellRangeAddress(firstRow, firstCol, lastRow, lastCol); + } + + /** + * Get the number of following ADDR structures. The number of this + * structures is automatically set when reading an Excel file and/or + * increased when you manually add a new ADDR structure . This is the reason + * there isn't a set method for this field . + * + * @return number of ADDR structures + */ + public int countRanges() { + return _list.size(); + } + + /** + * Add a cell range structure. + * + * @param firstRow - the upper left hand corner's row + * @param firstCol - the upper left hand corner's col + * @param lastRow - the lower right hand corner's row + * @param lastCol - the lower right hand corner's col + * @return the index of this ADDR structure + */ + public void addCellRangeAddress(int firstRow, int firstCol, int lastRow, int lastCol) { + CellRangeAddress region = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol); + addCellRangeAddress(region); + } + public void addCellRangeAddress(CellRangeAddress cra) { + _list.add(cra); + } + public CellRangeAddress remove(int rangeIndex) { + if (_list.isEmpty()) { + throw new RuntimeException("List is empty"); + } + if (rangeIndex < 0 || rangeIndex >= _list.size()) { + throw new RuntimeException("Range index (" + rangeIndex + + ") is outside allowable range (0.." + (_list.size()-1) + ")"); + } + return (CellRangeAddress) _list.remove(rangeIndex); + } + + /** + * @return CellRangeAddress at the given index + */ + public CellRangeAddress getCellRangeAddress(int index) { + return (CellRangeAddress) _list.get(index); + } + + public int getSize() { + return 2 + CellRangeAddress.getEncodedSize(_list.size()); + } + + public int serialize(int offset, byte[] data) { + int pos = 2; + + int nItems = _list.size(); + LittleEndian.putUShort(data, offset, nItems); + for (int k = 0; k < nItems; k++) { + CellRangeAddress region = (CellRangeAddress) _list.get(k); + pos += region.serialize(offset + pos, data); + } + return getSize(); + } + + public CellRangeAddressList copy() { + CellRangeAddressList result = new CellRangeAddressList(); + + int nItems = _list.size(); + for (int k = 0; k < nItems; k++) { + CellRangeAddress region = (CellRangeAddress) _list.get(k); + result.addCellRangeAddress(region.copy()); + } + return result; + } + public CellRangeAddress[] getCellRangeAddresses() { + CellRangeAddress[] result = new CellRangeAddress[_list.size()]; + _list.toArray(result); + return result; + } +} diff --git a/src/java/org/apache/poi/ss/util/Region.java b/src/java/org/apache/poi/ss/util/Region.java index 71dd31403..1ee64c5d0 100644 --- a/src/java/org/apache/poi/ss/util/Region.java +++ b/src/java/org/apache/poi/ss/util/Region.java @@ -15,10 +15,8 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.ss.util; -import org.apache.poi.hssf.record.MergeCellsRecord.MergedRegion; /** * Represents a from/to row/col square. This is a object primitive @@ -26,11 +24,9 @@ import org.apache.poi.hssf.record.MergeCellsRecord.MergedRegion; * to represent a string of characters. Its really only useful for HSSF though. * * @author Andrew C. Oliver acoliver at apache dot org + * @deprecated (Aug-2008) use {@link CellRangeAddress} */ - -public class Region - implements Comparable -{ +public class Region implements Comparable { private int rowFrom; private short colFrom; private int rowTo; @@ -52,16 +48,6 @@ public class Region this.colTo = colTo; } - /** - * special constructor (I know this is bad but it is so wrong that its right - * okay) that makes a region from a mergedcells's region subrecord. - */ - - public Region(MergedRegion region) - { - this(region.row_from, region.col_from, region.row_to, region.col_to); - } - public Region(String ref) { CellReference cellReferenceFrom = new CellReference(ref.substring(0, ref.indexOf(":"))); CellReference cellReferenceTo = new CellReference(ref.substring(ref.indexOf(":") + 1)); @@ -71,7 +57,8 @@ public class Region this.colTo = (short) cellReferenceTo.getCol(); } - /** + + /** * get the upper left hand corner column number * * @return column number for the upper left hand corner @@ -159,6 +146,7 @@ public class Region this.rowTo = rowTo; } + /** * Answers: "is the row/column inside this range?" * @@ -218,16 +206,51 @@ public class Region return compareTo(( Region ) o); } - /** - * @return the area contained by this region (number of cells) - */ + /** + * Convert a List of CellRange objects to an array of regions + * + * @param List of CellRange objects + * @return regions + */ + public static Region[] convertCellRangesToRegions(CellRangeAddress[] cellRanges) { + int size = cellRanges.length; + if(size < 1) { + return new Region[0]; + } + + Region[] result = new Region[size]; + + for (int i = 0; i != size; i++) { + result[i] = convertToRegion(cellRanges[i]); + } + return result; + } + + + + private static Region convertToRegion(CellRangeAddress cr) { + + return new Region(cr.getFirstRow(), (short)cr.getFirstColumn(), cr.getLastRow(), (short)cr.getLastColumn()); + } + + public static CellRangeAddress[] convertRegionsToCellRanges(Region[] regions) { + int size = regions.length; + if(size < 1) { + return new CellRangeAddress[0]; + } + + CellRangeAddress[] result = new CellRangeAddress[size]; + + for (int i = 0; i != size; i++) { + result[i] = convertToCellRangeAddress(regions[i]); + } + return result; + } + + public static CellRangeAddress convertToCellRangeAddress(Region r) { + return new CellRangeAddress(r.getRowFrom(), r.getRowTo(), r.getColumnFrom(), r.getColumnTo()); + } - public int getArea() - { - return ((1 + (getRowTo() - getRowFrom())) - * (1 + (getColumnTo() - getColumnFrom()))); - } - /** * @return the string reference for this region */ diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java new file mode 100644 index 000000000..3ebcecc90 --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java @@ -0,0 +1,51 @@ +/* ==================================================================== + 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.hslf.examples; + +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hslf.model.HeadersFooters; +import org.apache.poi.hslf.model.Slide; + +import java.io.FileOutputStream; + +/** + * Demonstrates how to set headers / footers + * + * @author Yegor Kozlov + */ +public class HeadersFootersDemo { + public static void main(String[] args) throws Exception { + SlideShow ppt = new SlideShow(); + + HeadersFooters slideHeaders = ppt.getSlideHeadersFooters(); + slideHeaders.setFootersText("Created by POI-HSLF"); + slideHeaders.setSlideNumberVisible(true); + slideHeaders.setDateTimeText("custom date time"); + + HeadersFooters notesHeaders = ppt.getNotesHeadersFooters(); + notesHeaders.setFootersText("My notes footers"); + notesHeaders.setHeaderText("My notes header"); + + Slide slide = ppt.createSlide(); + + FileOutputStream out = new FileOutputStream("headers_footers.ppt"); + ppt.write(out); + out.close(); + + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java new file mode 100644 index 000000000..e94bcb981 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java @@ -0,0 +1,226 @@ + +/* ==================================================================== + 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.hslf.model; + +import org.apache.poi.hslf.record.*; +import org.apache.poi.hslf.usermodel.SlideShow; + +/** + * Header / Footer settings. + * + * @author Yegor Kozlov + */ +public class HeadersFooters { + + private HeadersFootersContainer _container; + private boolean _newRecord; + private SlideShow _ppt; + + public HeadersFooters(HeadersFootersContainer rec, SlideShow ppt, boolean newRecord){ + _container = rec; + _newRecord = newRecord; + _ppt = ppt; + } + + /** + * Headers's text + * + * @return Headers's text + */ + public String getHeaderText(){ + CString cs = _container.getHeaderAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets headers's text + * + * @param text headers's text + */ + public void setHeaderText(String text){ + if(_newRecord) attach(); + + setHeaderVisible(true); + CString cs = _container.getHeaderAtom(); + if(cs == null) cs = _container.addHeaderAtom(); + + cs.setText(text); + } + + /** + * Footer's text + * + * @return Footer's text + */ + public String getFooterText(){ + CString cs = _container.getFooterAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets footers's text + * + * @param text footers's text + */ + public void setFootersText(String text){ + if(_newRecord) attach(); + + setFooterVisible(true); + CString cs = _container.getFooterAtom(); + if(cs == null) cs = _container.addFooterAtom(); + + cs.setText(text); + } + + /** + * This is the date that the user wants in the footers, instead of today's date. + * + * @return custom user date + */ + public String getDateTimeText(){ + CString cs = _container.getUserDateAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets custom user date to be displayed instead of today's date. + * + * @param text custom user date + */ + public void setDateTimeText(String text){ + if(_newRecord) attach(); + + setUserDateVisible(true); + setDateTimeVisible(true); + CString cs = _container.getUserDateAtom(); + if(cs == null) cs = _container.addUserDateAtom(); + + cs.setText(text); + } + + /** + * whether the footer text is displayed. + */ + public boolean isFooterVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasFooter); + } + + /** + * whether the footer text is displayed. + */ + public void setFooterVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasFooter, flag); + } + + /** + * whether the header text is displayed. + */ + public boolean isHeaderVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasHeader); + } + + /** + * whether the header text is displayed. + */ + public void setHeaderVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasHeader, flag); + } + + /** + * whether the date is displayed in the footer. + */ + public boolean isDateTimeVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasDate); + } + + /** + * whether the date is displayed in the footer. + */ + public void setDateTimeVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasDate, flag); + } + + /** + * whether the custom user date is used instead of today's date. + */ + public boolean isUserDateVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasUserDate); + } + + /** + * whether the date is displayed in the footer. + */ + public void setUserDateVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasUserDate, flag); + } + + /** + * whether the slide number is displayed in the footer. + */ + public boolean isSlideNumberVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasSlideNumber); + } + + /** + * whether the slide number is displayed in the footer. + */ + public void setSlideNumberVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasSlideNumber, flag); + } + + /** + * An integer that specifies the format ID to be used to style the datetime. + * + * @return an integer that specifies the format ID to be used to style the datetime. + */ + public int getDateTimeFormat(){ + return _container.getHeadersFootersAtom().getFormatId(); + } + + /** + * An integer that specifies the format ID to be used to style the datetime. + * + * @param formatId an integer that specifies the format ID to be used to style the datetime. + */ + public void setDateTimeFormat(int formatId){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFormatId(formatId); + } + + /** + * Attach this HeadersFootersContainer to the parent Document record + */ + private void attach(){ + Document doc = _ppt.getDocumentRecord(); + Record[] ch = doc.getChildRecords(); + Record lst = null; + for (int i=0; i < ch.length; i++){ + if(ch[i].getRecordType() == RecordTypes.List.typeID){ + lst = ch[i]; + break; + } + } + doc.addChildAfter(_container, lst); + _newRecord = false; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index ee32868eb..670a86655 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -24,9 +24,7 @@ import java.util.Vector; import java.util.Iterator; import java.awt.*; -import org.apache.poi.hslf.record.SlideAtom; -import org.apache.poi.hslf.record.TextHeaderAtom; -import org.apache.poi.hslf.record.ColorSchemeAtom; +import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; import org.apache.poi.ddf.EscherDggRecord; import org.apache.poi.ddf.EscherContainerRecord; @@ -381,4 +379,22 @@ public class Slide extends Sheet } } + /** + * Header / Footer settings for this slide. + * + * @return Header / Footer settings for this slide + */ + public HeadersFooters getHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = getSheetContainer().getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) return getSlideShow().getSlideHeadersFooters(); + else return new HeadersFooters(hdd, getSlideShow(), newRecord); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/CString.java b/src/scratchpad/src/org/apache/poi/hslf/record/CString.java index b17efc40b..e7e46baa0 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/CString.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/CString.java @@ -56,7 +56,7 @@ public class CString extends RecordAtom { * Grabs the count, from the first two bytes of the header. * The meaning of the count is specific to the type of the parent record */ - public int getCount() { + public int getOptions() { return (int)LittleEndian.getShort(_header); } @@ -64,7 +64,7 @@ public class CString extends RecordAtom { * Sets the count * The meaning of the count is specific to the type of the parent record */ - public void setCount(int count) { + public void setOptions(int count) { LittleEndian.putShort(_header, (short)count); } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java b/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java index 91ee8ea3a..31ef11a66 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java @@ -140,9 +140,9 @@ public class Comment2000 extends RecordContainer { CString csa = new CString(); CString csb = new CString(); CString csc = new CString(); - csa.setCount(0x00); - csb.setCount(0x10); - csc.setCount(0x20); + csa.setOptions(0x00); + csb.setOptions(0x10); + csc.setOptions(0x20); _children[0] = csa; _children[1] = csb; _children[2] = csc; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java index e0810dbca..fb669b38e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java @@ -25,6 +25,7 @@ import org.apache.poi.poifs.filesystem.*; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; +import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; /** @@ -39,14 +40,15 @@ public class CurrentUserAtom { /** Standard Atom header */ public static final byte[] atomHeader = new byte[] { 0, 0, -10, 15 }; - /** The Powerpoint magic numer */ - public static final byte[] magicNumber = new byte[] { 95, -64, -111, -29 }; + /** The PowerPoint magic number for a non-encrypted file */ + public static final byte[] headerToken = new byte[] { 95, -64, -111, -29 }; + /** The PowerPoint magic number for an encrytpted file */ + public static final byte[] encHeaderToken = new byte[] { -33, -60, -47, -13 }; /** The Powerpoint 97 version, major and minor numbers */ public static final byte[] ppt97FileVer = new byte[] { 8, 00, -13, 03, 03, 00 }; /** The version, major and minor numbers */ - private int docFinalVersionA; - private int docFinalVersionB; + private int docFinalVersion; private byte docMajorNo; private byte docMinorNo; @@ -54,7 +56,7 @@ public class CurrentUserAtom private long currentEditOffset; /** The Username of the last person to edit the file */ private String lastEditUser; - /** The document release version */ + /** The document release version. Almost always 8 */ private long releaseVersion; /** Only correct after reading in or writing out */ @@ -63,8 +65,7 @@ public class CurrentUserAtom /* ********************* getter/setter follows *********************** */ - public int getDocFinalVersionA() { return docFinalVersionA; } - public int getDocFinalVersionB() { return docFinalVersionB; } + public int getDocFinalVersion() { return docFinalVersion; } public byte getDocMajorNo() { return docMajorNo; } public byte getDocMinorNo() { return docMinorNo; } @@ -86,7 +87,14 @@ public class CurrentUserAtom */ public CurrentUserAtom() { _contents = new byte[0]; - throw new RuntimeException("Creation support for Current User Atom not complete"); + + // Initialise to empty + docFinalVersion = 0x03f4; + docMajorNo = 3; + docMinorNo = 0; + releaseVersion = 8; + currentEditOffset = 0; + lastEditUser = "Apache POI"; } /** @@ -130,12 +138,20 @@ public class CurrentUserAtom * Actually do the creation from a block of bytes */ private void init() { + // First up is the size, in 4 bytes, which is fixed + // Then is the header - check for encrypted + if(_contents[12] == encHeaderToken[0] && + _contents[13] == encHeaderToken[1] && + _contents[14] == encHeaderToken[2] && + _contents[15] == encHeaderToken[3]) { + throw new EncryptedPowerPointFileException("The CurrentUserAtom specifies that the document is encrypted"); + } + // Grab the edit offset currentEditOffset = LittleEndian.getUInt(_contents,16); // Grab the versions - docFinalVersionA = LittleEndian.getUShort(_contents,20); - docFinalVersionB = LittleEndian.getUShort(_contents,22); + docFinalVersion = LittleEndian.getUShort(_contents,22); docMajorNo = _contents[24]; docMinorNo = _contents[25]; @@ -194,15 +210,22 @@ public class CurrentUserAtom // Now we have the size of the details, which is 20 LittleEndian.putInt(_contents,8,20); - // Now the ppt magic number (4 bytes) - System.arraycopy(magicNumber,0,_contents,12,4); + // Now the ppt un-encrypted header token (4 bytes) + System.arraycopy(headerToken,0,_contents,12,4); // Now the current edit offset LittleEndian.putInt(_contents,16,(int)currentEditOffset); - // Now the file versions, 2+2+1+1 - LittleEndian.putShort(_contents,20,(short)docFinalVersionA); - LittleEndian.putShort(_contents,22,(short)docFinalVersionB); + // The username gets stored twice, once as US + // ascii, and again as unicode laster on + byte[] asciiUN = new byte[lastEditUser.length()]; + StringUtil.putCompressedUnicode(lastEditUser,asciiUN,0); + + // Now we're able to do the length of the last edited user + LittleEndian.putShort(_contents,20,(short)asciiUN.length); + + // Now the file versions, 2+1+1 + LittleEndian.putShort(_contents,22,(short)docFinalVersion); _contents[24] = docMajorNo; _contents[25] = docMinorNo; @@ -210,9 +233,7 @@ public class CurrentUserAtom _contents[26] = 0; _contents[27] = 0; - // username in bytes in us ascii - byte[] asciiUN = new byte[lastEditUser.length()]; - StringUtil.putCompressedUnicode(lastEditUser,asciiUN,0); + // At this point we have the username as us ascii System.arraycopy(asciiUN,0,_contents,28,asciiUN.length); // 4 byte release version diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java index 501712e9d..6d61f2ef7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java @@ -74,8 +74,8 @@ public class ExEmbed extends RecordContainer { CString cs1 = new CString(); CString cs2 = new CString(); CString cs3 = new CString(); -// cs1.setCount(0x00); -// cs2.setCount(0x10); +// cs1.setOptions(0x00); +// cs2.setOptions(0x10); _children[0] = new ExEmbedAtom(); _children[1] = new ExOleObjAtom(); _children[2] = cs1; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java index 8ba58cdb6..b0bc1e191 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java @@ -136,8 +136,8 @@ public class ExHyperlink extends RecordContainer { // Setup our child records CString csa = new CString(); CString csb = new CString(); - csa.setCount(0x00); - csb.setCount(0x10); + csa.setOptions(0x00); + csb.setOptions(0x10); _children[0] = new ExHyperlinkAtom(); _children[1] = csa; _children[2] = csb; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java new file mode 100644 index 000000000..a9457a6de --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java @@ -0,0 +1,207 @@ + +/* ==================================================================== + 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.hslf.record; + +import org.apache.poi.util.LittleEndian; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An atom record that specifies options for displaying headers and footers + * on a presentation slide or notes slide. + * + * @author Yegor Kozlov + */ + +public class HeadersFootersAtom extends RecordAtom { + + /** + * A bit that specifies whether the date is displayed in the footer. + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasDate = 1; + + /** + * A bit that specifies whether the current datetime is used for displaying the datetime. + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasTodayDate = 2; + + /** + * A bit that specifies whether the date specified in UserDateAtom record + * is used for displaying the datetime. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasUserDate = 4; + + /** + * A bit that specifies whether the slide number is displayed in the footer. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasSlideNumber = 8; + + /** + * bit that specifies whether the header text is displayed. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasHeader = 16; + + /** + * bit that specifies whether the footer text is displayed. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasFooter = 32; + + /** + * record header + */ + private byte[] _header; + + /** + * record data + */ + private byte[] _recdata; + + /** + * Build an instance of HeadersFootersAtom from on-disk data + */ + protected HeadersFootersAtom(byte[] source, int start, int len) { + // Get the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Grab the record data + _recdata = new byte[len-8]; + System.arraycopy(source,start+8,_recdata,0,len-8); + } + + /** + * Create a new instance of HeadersFootersAtom + */ + public HeadersFootersAtom() { + _recdata = new byte[4]; + + _header = new byte[8]; + LittleEndian.putShort(_header, 2, (short)getRecordType()); + LittleEndian.putInt(_header, 4, _recdata.length); + } + + public long getRecordType() { + return RecordTypes.HeadersFootersAtom.typeID; + } + + /** + * Write the contents of the record back, so it can be written to disk + */ + public void writeOut(OutputStream out) throws IOException { + out.write(_header); + out.write(_recdata); + } + + /** + * A signed integer that specifies the format ID to be used to style the datetime. + *

    + * It MUST be in the range [0, 12].
    + * This value is converted into a string as specified by the index field of the DateTimeMCAtom record. + * It MUST be ignored unless fHasTodayDate is TRUE. + * + * + * @return A signed integer that specifies the format ID to be used to style the datetime. + */ + public int getFormatId(){ + return LittleEndian.getShort(_recdata, 0); + } + + /** + * A signed integer that specifies the format ID to be used to style the datetime. + * + * @param formatId A signed integer that specifies the format ID to be used to style the datetime. + */ + public void setFormatId(int formatId){ + LittleEndian.putUShort(_recdata, 0, formatId); + } + + /** + * A bit mask specifying options for displaying headers and footers + * + *

  • A - {@link #fHasDate} (1 bit): A bit that specifies whether the date is displayed in the footer. + *
  • B - {@link #fHasTodayDate} (1 bit): A bit that specifies whether the current datetime is used for + * displaying the datetime. + *
  • C - {@link #fHasUserDate} (1 bit): A bit that specifies whether the date specified in UserDateAtom record + * is used for displaying the datetime. + *
  • D - {@link #fHasSlideNumber} (1 bit): A bit that specifies whether the slide number is displayed in the footer. + *
  • E - {@link #fHasHeader} (1 bit): A bit that specifies whether the header text specified by HeaderAtom + * record is displayed. + *
  • F - {@link #fHasFooter} (1 bit): A bit that specifies whether the footer text specified by FooterAtom + * record is displayed. + *
  • reserved (10 bits): MUST be zero and MUST be ignored. + * + * @return A bit mask specifying options for displaying headers and footers + */ + public int getMask(){ + return LittleEndian.getShort(_recdata, 2); + } + + /** + * A bit mask specifying options for displaying headers and footers + * + * @param mask A bit mask specifying options for displaying headers and footers + */ + public void setMask(int mask){ + LittleEndian.putUShort(_recdata, 2, mask); + } + + /** + * @param bit the bit to check + * @return whether the specified flag is set + */ + public boolean getFlag(int bit){ + return (getMask() & bit) != 0; + } + + /** + * @param bit the bit to set + * @param value whether the specified bit is set + */ + public void setFlag(int bit, boolean value){ + int mask = getMask(); + if(value) mask |= bit; + else mask &= ~bit; + setMask(mask); + } + + public String toString(){ + StringBuffer buf = new StringBuffer(); + buf.append("HeadersFootersAtom\n"); + buf.append("\tFormatId: " + getFormatId() + "\n"); + buf.append("\tMask : " + getMask() + "\n"); + buf.append("\t fHasDate : " + getFlag(fHasDate) + "\n"); + buf.append("\t fHasTodayDate : " + getFlag(fHasTodayDate) + "\n"); + buf.append("\t fHasUserDate : " + getFlag(fHasUserDate) + "\n"); + buf.append("\t fHasSlideNumber : " + getFlag(fHasSlideNumber) + "\n"); + buf.append("\t fHasHeader : " + getFlag(fHasHeader) + "\n"); + buf.append("\t fHasFooter : " + getFlag(fHasFooter) + "\n"); + return buf.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java new file mode 100644 index 000000000..e121574bc --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java @@ -0,0 +1,212 @@ + +/* ==================================================================== + 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.hslf.record; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * A container record that specifies information about the footers on a presentation slide. + *

    + * It contains:
    + *

  • 1. {@link HeadersFootersAtom} + *
  • 2. {@link CString }, Instance UserDate (0), optional: Stores the user's date. + * This is the date that the user wants in the footers, instead of today's date. + *
  • 3. {@link CString }, Instance Header (1), optional: Stores the Header's contents. + *
  • 4. {@link CString }, Instance Footer (2), optional: Stores the Footer's contents. + *

    + * + * @author Yegor Kozlov + */ +public class HeadersFootersContainer extends RecordContainer { + + /** + * "instance" field in the record header indicating that this HeadersFootersContaine + * is applied for slides + */ + public static final short SlideHeadersFootersContainer = 0x3F; + /** + * "instance" field in the record header indicating that this HeadersFootersContaine + * is applied for notes and handouts + */ + public static final short NotesHeadersFootersContainer = 0x4F; + + public static final int USERDATEATOM = 0; + public static final int HEADERATOM = 1; + public static final int FOOTERATOM = 2; + + private byte[] _header; + private HeadersFootersAtom hdAtom; + private CString csDate, csHeader, csFooter; + + protected HeadersFootersContainer(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + _children = Record.findChildRecords(source,start+8,len-8); + for(int i=0; i < _children.length; i++){ + if(_children[i] instanceof HeadersFootersAtom) hdAtom = (HeadersFootersAtom)_children[i]; + else if(_children[i] instanceof CString) { + CString cs = (CString)_children[i]; + int opts = cs.getOptions() >> 4; + switch(opts){ + case USERDATEATOM: csDate = cs; break; + case HEADERATOM: csHeader = cs; break; + case FOOTERATOM: csFooter = cs; break; + default: + logger.log(POILogger.WARN, "Unexpected CString.Options in HeadersFootersContainer: " + opts); + break; + } + } else { + logger.log(POILogger.WARN, "Unexpected record in HeadersFootersContainer: " + _children[i]); + } + } + + } + + public HeadersFootersContainer(short options) { + _header = new byte[8]; + LittleEndian.putShort(_header, 0, options); + LittleEndian.putShort(_header, 2, (short)getRecordType()); + + hdAtom = new HeadersFootersAtom(); + _children = new Record[]{ + hdAtom + }; + csDate = csHeader = csFooter = null; + + } + + /** + * Return the type, which is {@link RecordTypes#HeadersFooters} + */ + public long getRecordType() { + return RecordTypes.HeadersFooters.typeID; + } + + /** + * Must be either {@link #SlideHeadersFootersContainer} or {@link #NotesHeadersFootersContainer} + * + * @return "instance" field in the record header + */ + public int getOptions(){ + return LittleEndian.getShort(_header, 0); + } + + /** + * Write the contents of the record back, so it can be written to disk + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } + + /** + * HeadersFootersAtom stores the basic information of the header and footer structure. + * + * @return HeadersFootersAtom + */ + public HeadersFootersAtom getHeadersFootersAtom(){ + return hdAtom; + } + + /** + * A {@link CString} record that stores the user's date. + *

    This is the date that the user wants in the footers, instead of today's date.

    + * + * @return A {@link CString} record that stores the user's date or null + */ + public CString getUserDateAtom(){ + return csDate; + } + + /** + * A {@link CString} record that stores the Header's contents. + * + * @return A {@link CString} record that stores the Header's contents or null + */ + public CString getHeaderAtom(){ + return csHeader; + } + + /** + * A {@link CString} record that stores the Footers's contents. + * + * @return A {@link CString} record that stores the Footers's contents or null + */ + public CString getFooterAtom(){ + return csFooter; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addUserDateAtom(){ + if(csDate != null) return csDate; + + csDate = new CString(); + csDate.setOptions(USERDATEATOM << 4); + + addChildAfter(csDate, hdAtom); + + return csDate; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addHeaderAtom(){ + if(csHeader != null) return csHeader; + + csHeader = new CString(); + csHeader.setOptions(HEADERATOM << 4); + + Record r = hdAtom; + if(csDate != null) r = hdAtom; + addChildAfter(csHeader, r); + + return csHeader; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addFooterAtom(){ + if(csFooter != null) return csFooter; + + csFooter = new CString(); + csFooter.setOptions(FOOTERATOM << 4); + + Record r = hdAtom; + if(csHeader != null) r = csHeader; + else if(csDate != null) r = csDate; + addChildAfter(csFooter, r); + + return csFooter; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index a6f00da12..d7a664725 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -111,8 +111,8 @@ public class RecordTypes { public static final Type ExHyperlinkAtom = new Type(4051,ExHyperlinkAtom.class); public static final Type ExHyperlink = new Type(4055,ExHyperlink.class); public static final Type SlideNumberMCAtom = new Type(4056,null); - public static final Type HeadersFooters = new Type(4057,null); - public static final Type HeadersFootersAtom = new Type(4058,null); + public static final Type HeadersFooters = new Type(4057,HeadersFootersContainer.class); + public static final Type HeadersFootersAtom = new Type(4058,HeadersFootersAtom.class); public static final Type TxInteractiveInfoAtom = new Type(4063,TxInteractiveInfoAtom.class); public static final Type CharFormatAtom = new Type(4066,null); public static final Type ParaFormatAtom = new Type(4067,null); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 56e94431e..f38cc7716 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -811,4 +811,50 @@ public class SlideShow public int getNumberOfFonts() { return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts(); } + + /** + * Return Header / Footer settings for slides + * + * @return Header / Footer settings for slides + */ + public HeadersFooters getSlideHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = _documentRecord.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer && + ((HeadersFootersContainer)ch[i]).getOptions() == HeadersFootersContainer.SlideHeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) { + hdd = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer); + newRecord = true; + } + return new HeadersFooters(hdd, this, newRecord); + } + + /** + * Return Header / Footer settings for notes + * + * @return Header / Footer settings for notes + */ + public HeadersFooters getNotesHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = _documentRecord.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer && + ((HeadersFootersContainer)ch[i]).getOptions() == HeadersFootersContainer.NotesHeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) { + hdd = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer); + newRecord = true; + } + return new HeadersFooters(hdd, this, newRecord); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt new file mode 100644 index 000000000..891d73f95 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java new file mode 100644 index 000000000..8b1cdbe09 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java @@ -0,0 +1,118 @@ + +/* ==================================================================== + 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.hslf.model; + +import java.io.*; +import org.apache.poi.hslf.usermodel.SlideShow; + +import junit.framework.TestCase; + +/** + * Test {@link org.apache.poi.hslf.model.HeadersFooters} object + */ +public class TestHeadersFooters extends TestCase +{ + + public static final String cwd = System.getProperty("HSLF.testdata.path"); + + public void testRead() throws Exception + { + File file = new File(cwd, "headers_footers.ppt"); + FileInputStream is = new FileInputStream(file); + SlideShow ppt = new SlideShow(is); + is.close(); + + HeadersFooters slideHdd = ppt.getSlideHeadersFooters(); + assertTrue(slideHdd.isFooterVisible()); + assertEquals("Global Slide Footer", slideHdd.getFooterText()); + assertTrue(slideHdd.isSlideNumberVisible()); + assertFalse(slideHdd.isHeaderVisible()); + assertNull(slideHdd.getHeaderText()); + assertFalse(slideHdd.isUserDateVisible()); + assertNull(slideHdd.getDateTimeText()); + + + HeadersFooters notesHdd = ppt.getNotesHeadersFooters(); + assertTrue(notesHdd.isFooterVisible()); + assertEquals("Notes Footer", notesHdd.getFooterText()); + assertTrue(notesHdd.isHeaderVisible()); + assertEquals("Notes Header", notesHdd.getHeaderText()); + assertTrue(notesHdd.isUserDateVisible()); + assertNull(notesHdd.getDateTimeText()); + + Slide[] slide = ppt.getSlides(); + //the first slide uses presentation-scope headers / footers + HeadersFooters hd1 = slide[0].getHeadersFooters(); + assertEquals(slideHdd.isFooterVisible(), hd1.isFooterVisible()); + assertEquals(slideHdd.getFooterText(), hd1.getFooterText()); + assertEquals(slideHdd.isSlideNumberVisible(), hd1.isSlideNumberVisible()); + assertEquals(slideHdd.isHeaderVisible(), hd1.isHeaderVisible()); + assertEquals(slideHdd.getHeaderText(), hd1.getHeaderText()); + assertEquals(slideHdd.isUserDateVisible(), hd1.isUserDateVisible()); + assertEquals(slideHdd.getDateTimeText(), hd1.getDateTimeText()); + + //the first slide uses per-slide headers / footers + HeadersFooters hd2 = slide[1].getHeadersFooters(); + assertEquals(true, hd2.isFooterVisible()); + assertEquals("per-slide footer", hd2.getFooterText()); + assertEquals(true, hd2.isUserDateVisible()); + assertEquals("custom date format", hd2.getDateTimeText()); + } + + public void testCreateSlideFooters() throws Exception + { + SlideShow ppt = new SlideShow(); + HeadersFooters hdd = ppt.getSlideHeadersFooters(); + hdd.setFootersText("My slide footer"); + hdd.setSlideNumberVisible(true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + byte[] b = out.toByteArray(); + + SlideShow ppt2 = new SlideShow(new ByteArrayInputStream(b)); + HeadersFooters hdd2 = ppt2.getSlideHeadersFooters(); + assertTrue(hdd2.isSlideNumberVisible()); + assertTrue(hdd2.isFooterVisible()); + assertEquals("My slide footer", hdd2.getFooterText()); + } + + public void testCreateNotesFooters() throws Exception + { + SlideShow ppt = new SlideShow(); + HeadersFooters hdd = ppt.getNotesHeadersFooters(); + hdd.setFootersText("My notes footer"); + hdd.setHeaderText("My notes header"); + hdd.setSlideNumberVisible(true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + byte[] b = out.toByteArray(); + + SlideShow ppt2 = new SlideShow(new ByteArrayInputStream(b)); + HeadersFooters hdd2 = ppt2.getNotesHeadersFooters(); + assertTrue(hdd2.isSlideNumberVisible()); + assertTrue(hdd2.isFooterVisible()); + assertEquals("My notes footer", hdd2.getFooterText()); + assertTrue(hdd2.isHeaderVisible()); + assertEquals("My notes header", hdd2.getHeaderText()); + } +} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java index 3f3d1a144..df8e1a3da 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java @@ -46,12 +46,12 @@ public class TestCString extends TestCase { } public void testCount() throws Exception { CString ca = new CString(data_a, 0, data_a.length); - assertEquals(0, ca.getCount()); + assertEquals(0, ca.getOptions()); CString cb = new CString(data_b, 0, data_a.length); - assertEquals(0x10, cb.getCount()); + assertEquals(0x10, cb.getOptions()); - ca.setCount(28); - assertEquals(28, ca.getCount()); + ca.setOptions(28); + assertEquals(28, ca.getOptions()); } public void testText() throws Exception { @@ -90,7 +90,7 @@ public class TestCString extends TestCase { public void testChange() throws Exception { CString ca = new CString(data_a, 0, data_a.length); ca.setText("Comments"); - ca.setCount(0x10); + ca.setOptions(0x10); try { for(int i=0; i> 4); + + assertEquals("My Footer - 1", csFooter.getText()); + + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + } + + public void testWriteSlideHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(slideData, 0, slideData.length); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(slideData, b)); + } + + public void testNewSlideHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer); + + assertNotNull(record.getHeadersFootersAtom()); + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + assertNull(record.getFooterAtom()); + + HeadersFootersAtom hd = record.getHeadersFootersAtom(); + hd.setFlag(HeadersFootersAtom.fHasDate, true); + hd.setFlag(HeadersFootersAtom.fHasTodayDate, true); + hd.setFlag(HeadersFootersAtom.fHasFooter, true); + + CString csFooter = record.addFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + csFooter.setText("My Footer - 1"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(slideData, b)); + } + + public void testReadNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(notesData, 0, notesData.length); + assertEquals(RecordTypes.HeadersFooters.typeID, record.getRecordType()); + assertEquals(HeadersFootersContainer.NotesHeadersFootersContainer, record.getOptions()); + assertEquals(3, record.getChildRecords().length); + + HeadersFootersAtom hdd = record.getHeadersFootersAtom(); + assertNotNull(hdd); + + CString csHeader = record.getHeaderAtom(); + assertNotNull(csHeader); + assertEquals(HeadersFootersContainer.HEADERATOM, csHeader.getOptions() >> 4); + assertEquals("Note Header", csHeader.getText()); + + CString csFooter = record.getFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + assertEquals("Note Footer", csFooter.getText()); + } + + public void testWriteNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(notesData, 0, notesData.length); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(notesData, b)); + } + + public void testNewNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer); + + assertNotNull(record.getHeadersFootersAtom()); + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + assertNull(record.getFooterAtom()); + + HeadersFootersAtom hd = record.getHeadersFootersAtom(); + hd.setFlag(HeadersFootersAtom.fHasDate, true); + hd.setFlag(HeadersFootersAtom.fHasTodayDate, false); + hd.setFlag(HeadersFootersAtom.fHasUserDate, true); + hd.setFlag(HeadersFootersAtom.fHasSlideNumber, true); + hd.setFlag(HeadersFootersAtom.fHasHeader, true); + hd.setFlag(HeadersFootersAtom.fHasFooter, true); + + CString csHeader = record.addHeaderAtom(); + assertNotNull(csHeader); + assertEquals(HeadersFootersContainer.HEADERATOM, csHeader.getOptions() >> 4); + csHeader.setText("Note Header"); + + CString csFooter = record.addFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + csFooter.setText("Note Footer"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(notesData, b)); + } + +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/hssf/data/dvEmpty.xls b/src/testcases/org/apache/poi/hssf/data/dvEmpty.xls new file mode 100644 index 000000000..72c78cfa4 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/dvEmpty.xls differ diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index ef21cc9b3..71881acce 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -297,7 +297,8 @@ public final class TestSheet extends TestCase { xfindex = sheet.getXFIndexForColAt((short) 1); assertEquals(DEFAULT_IDX, xfindex); - ColumnInfoRecord nci = ( ColumnInfoRecord ) sheet.createColInfo(); + // TODO change return type to ColumnInfoRecord + ColumnInfoRecord nci = (ColumnInfoRecord)ColumnInfoRecordsAggregate.createColInfo(); sheet.columns.insertColumn(nci); // single column ColumnInfoRecord diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheetAdditional.java b/src/testcases/org/apache/poi/hssf/model/TestSheetAdditional.java index f1c3b7c9c..f86c2941a 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheetAdditional.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheetAdditional.java @@ -17,13 +17,10 @@ package org.apache.poi.hssf.model; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - import junit.framework.TestCase; import org.apache.poi.hssf.record.ColumnInfoRecord; +import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; /** * @author Tony Poppleton @@ -32,7 +29,8 @@ public final class TestSheetAdditional extends TestCase { public void testGetCellWidth() { Sheet sheet = Sheet.createSheet(); - ColumnInfoRecord nci = ( ColumnInfoRecord ) sheet.createColInfo(); + // TODO change return type to ColumnInfoRecord + ColumnInfoRecord nci = (ColumnInfoRecord)ColumnInfoRecordsAggregate.createColInfo(); // Prepare test model nci.setFirstColumn((short)5); diff --git a/src/testcases/org/apache/poi/hssf/record/TestCFHeaderRecord.java b/src/testcases/org/apache/poi/hssf/record/TestCFHeaderRecord.java index e88350367..ec291ffd4 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestCFHeaderRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestCFHeaderRecord.java @@ -20,7 +20,7 @@ package org.apache.poi.hssf.record; import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.apache.poi.hssf.record.cf.CellRange; +import org.apache.poi.ss.util.CellRangeAddress; /** * Tests the serialization and deserialization of the TestCFHeaderRecord @@ -34,18 +34,18 @@ public final class TestCFHeaderRecord extends TestCase public void testCreateCFHeaderRecord () { CFHeaderRecord record = new CFHeaderRecord(); - CellRange[] ranges = { - new CellRange(0,0xFFFF,5,5), - new CellRange(0,0xFFFF,6,6), - new CellRange(0,1,0,1), - new CellRange(0,1,2,3), - new CellRange(2,3,0,1), - new CellRange(2,3,2,3), + CellRangeAddress[] ranges = { + new CellRangeAddress(0,0xFFFF,5,5), + new CellRangeAddress(0,0xFFFF,6,6), + new CellRangeAddress(0,1,0,1), + new CellRangeAddress(0,1,2,3), + new CellRangeAddress(2,3,0,1), + new CellRangeAddress(2,3,2,3), }; record.setCellRanges(ranges); ranges = record.getCellRanges(); assertEquals(6,ranges.length); - CellRange enclosingCellRange = record.getEnclosingCellRange(); + CellRangeAddress enclosingCellRange = record.getEnclosingCellRange(); assertEquals(0, enclosingCellRange.getFirstRow()); assertEquals(65535, enclosingCellRange.getLastRow()); assertEquals(0, enclosingCellRange.getFirstColumn()); @@ -95,7 +95,7 @@ public final class TestCFHeaderRecord extends TestCase assertEquals("#CFRULES", 3, record.getNumberOfConditionalFormats()); assertTrue(record.getNeedRecalculation()); confirm(record.getEnclosingCellRange(), 0, 3, 0, 3); - CellRange[] ranges = record.getCellRanges(); + CellRangeAddress[] ranges = record.getCellRanges(); assertEquals(4, ranges.length); confirm(ranges[0], 0, 1, 0, 1); confirm(ranges[1], 0, 1, 2, 3); @@ -154,7 +154,7 @@ public final class TestCFHeaderRecord extends TestCase assertEquals("#CFRULES", 19, record.getNumberOfConditionalFormats()); assertFalse(record.getNeedRecalculation()); confirm(record.getEnclosingCellRange(), 0, 65535, 0, 255); - CellRange[] ranges = record.getCellRanges(); + CellRangeAddress[] ranges = record.getCellRanges(); assertEquals(3, ranges.length); confirm(ranges[0], 40000, 50000, 2, 2); confirm(ranges[1], 0, 65535, 5, 5); @@ -168,18 +168,11 @@ public final class TestCFHeaderRecord extends TestCase assertEquals("CFHeaderRecord doesn't match", recordData[i], output[i+4]); } } - - private static void confirm(CellRange cr, int expFirstRow, int expLastRow, int expFirstCol, int expLastColumn) { + private static void confirm(CellRangeAddress cr, int expFirstRow, int expLastRow, int expFirstCol, int expLastColumn) { assertEquals("first row", expFirstRow, cr.getFirstRow()); assertEquals("last row", expLastRow, cr.getLastRow()); assertEquals("first column", expFirstCol, cr.getFirstColumn()); assertEquals("last column", expLastColumn, cr.getLastColumn()); } - - public static void main(String[] ignored_args) - { - System.out.println("Testing org.apache.poi.hssf.record.CFHeaderRecord"); - junit.textui.TestRunner.run(TestCFHeaderRecord.class); - } } diff --git a/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java b/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java index 31ec27b7c..9df7e3b26 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,19 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.record; import junit.framework.TestCase; -import org.apache.poi.hssf.record.MergeCellsRecord.MergedRegion; +import org.apache.poi.ss.util.CellRangeAddress; /** * Make sure the merge cells record behaves * @author Danny Mui (dmui at apache dot org) * */ -public class TestMergeCellsRecord extends TestCase { +public final class TestMergeCellsRecord extends TestCase { /** * Make sure when a clone is called, we actually clone it. @@ -40,13 +39,13 @@ public class TestMergeCellsRecord extends TestCase { assertNotSame("Merged and cloned objects are the same", merge, clone); - MergedRegion mergeRegion = merge.getAreaAt(0); - MergedRegion cloneRegion = clone.getAreaAt(0); + CellRangeAddress mergeRegion = merge.getAreaAt(0); + CellRangeAddress cloneRegion = clone.getAreaAt(0); assertNotSame("Should not point to same objects when cloning", mergeRegion, cloneRegion); - assertEquals("New Clone Row From doesnt match", mergeRegion.row_from, cloneRegion.row_from); - assertEquals("New Clone Row To doesnt match", mergeRegion.row_to, cloneRegion.row_to); - assertEquals("New Clone Col From doesnt match", mergeRegion.col_from, cloneRegion.col_from); - assertEquals("New Clone Col To doesnt match", mergeRegion.col_to, cloneRegion.col_to); + assertEquals("New Clone Row From doesnt match", mergeRegion.getFirstRow(), cloneRegion.getFirstRow()); + assertEquals("New Clone Row To doesnt match", mergeRegion.getLastRow(), cloneRegion.getLastRow()); + assertEquals("New Clone Col From doesnt match", mergeRegion.getFirstColumn(), cloneRegion.getFirstColumn()); + assertEquals("New Clone Col To doesnt match", mergeRegion.getLastColumn(), cloneRegion.getLastColumn()); merge.removeAreaAt(0); assertNotNull("Clone's item not removed", clone.getAreaAt(0)); diff --git a/src/testcases/org/apache/poi/hssf/record/aggregates/TestCFRecordsAggregate.java b/src/testcases/org/apache/poi/hssf/record/aggregates/TestCFRecordsAggregate.java index 2ae8230d5..efe352b24 100644 --- a/src/testcases/org/apache/poi/hssf/record/aggregates/TestCFRecordsAggregate.java +++ b/src/testcases/org/apache/poi/hssf/record/aggregates/TestCFRecordsAggregate.java @@ -28,8 +28,8 @@ import org.apache.poi.hssf.record.CFHeaderRecord; import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.RecordFactory; import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator; -import org.apache.poi.hssf.record.cf.CellRange; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.util.CellRangeAddress; /** * Tests the serialization and deserialization of the CFRecordsAggregate @@ -49,9 +49,9 @@ public final class TestCFRecordsAggregate extends TestCase CFRuleRecord rule2 = CFRuleRecord.create(workbook, ComparisonOperator.BETWEEN, "2", "5"); CFRuleRecord rule3 = CFRuleRecord.create(workbook, ComparisonOperator.GE, "100", null); header.setNumberOfConditionalFormats(3); - CellRange[] cellRanges = { - new CellRange(0,1,0,0), - new CellRange(0,1,2,2), + CellRangeAddress[] cellRanges = { + new CellRangeAddress(0,1,0,0), + new CellRangeAddress(0,1,2,2), }; header.setCellRanges(cellRanges); recs.add(header); @@ -97,11 +97,4 @@ public final class TestCFRecordsAggregate extends TestCase assertEquals(2, cellRanges.length); assertEquals(3, header.getNumberOfConditionalFormats()); } - - public static void main(String[] ignored_args) - { - System.out.println("Testing org.apache.poi.hssf.record.aggregates.CFRecordsAggregate"); - junit.textui.TestRunner.run(TestCFRecordsAggregate.class); - } - } diff --git a/src/testcases/org/apache/poi/hssf/record/cf/TestCellRange.java b/src/testcases/org/apache/poi/hssf/record/cf/TestCellRange.java index 2ba196d55..e01bc2ff7 100644 --- a/src/testcases/org/apache/poi/hssf/record/cf/TestCellRange.java +++ b/src/testcases/org/apache/poi/hssf/record/cf/TestCellRange.java @@ -17,6 +17,8 @@ limitations under the License. package org.apache.poi.hssf.record.cf; +import org.apache.poi.ss.util.CellRangeAddress; + import junit.framework.AssertionFailedError; import junit.framework.TestCase; @@ -25,15 +27,15 @@ import junit.framework.TestCase; */ public final class TestCellRange extends TestCase { - private static final CellRange biggest = createCR( 0, -1, 0,-1); - private static final CellRange tenthColumn = createCR( 0, -1,10,10); - private static final CellRange tenthRow = createCR(10, 10, 0,-1); - private static final CellRange box10x10 = createCR( 0, 10, 0,10); - private static final CellRange box9x9 = createCR( 0, 9, 0, 9); - private static final CellRange box10to20c = createCR( 0, 10,10,20); - private static final CellRange oneCell = createCR(10, 10,10,10); + private static final CellRangeAddress biggest = createCR( 0, -1, 0,-1); + private static final CellRangeAddress tenthColumn = createCR( 0, -1,10,10); + private static final CellRangeAddress tenthRow = createCR(10, 10, 0,-1); + private static final CellRangeAddress box10x10 = createCR( 0, 10, 0,10); + private static final CellRangeAddress box9x9 = createCR( 0, 9, 0, 9); + private static final CellRangeAddress box10to20c = createCR( 0, 10,10,20); + private static final CellRangeAddress oneCell = createCR(10, 10,10,10); - private static final CellRange[] sampleRanges = { + private static final CellRangeAddress[] sampleRanges = { biggest, tenthColumn, tenthRow, box10x10, box9x9, box10to20c, oneCell, }; @@ -54,9 +56,9 @@ public final class TestCellRange extends TestCase * @param lastRow pass -1 for max row index * @param lastCol pass -1 for max col index */ - private static CellRange createCR(int firstRow, int lastRow, int firstCol, int lastCol) { + private static CellRangeAddress createCR(int firstRow, int lastRow, int firstCol, int lastCol) { // max row & max col limit as per BIFF8 - return new CellRange( + return new CellRangeAddress( firstRow, lastRow == -1 ? 0xFFFF : lastRow, firstCol, @@ -65,89 +67,89 @@ public final class TestCellRange extends TestCase public void testContainsMethod() { - CellRange [] ranges = sampleRanges; + CellRangeAddress [] ranges = sampleRanges; for(int i=0; i!=ranges.length;i++) { for(int j=0; j!=ranges.length;j++) { boolean expectedResult = containsExpectedResults[i][j]; - assertEquals("("+i+","+j+"): ", expectedResult, ranges[i].contains(ranges[j])); + assertEquals("("+i+","+j+"): ", expectedResult, CellRangeUtil.contains(ranges[i], ranges[j])); } } } - private static final CellRange col1 = createCR( 0, -1, 1,1); - private static final CellRange col2 = createCR( 0, -1, 2,2); - private static final CellRange row1 = createCR( 1, 1, 0,-1); - private static final CellRange row2 = createCR( 2, 2, 0,-1); + private static final CellRangeAddress col1 = createCR( 0, -1, 1,1); + private static final CellRangeAddress col2 = createCR( 0, -1, 2,2); + private static final CellRangeAddress row1 = createCR( 1, 1, 0,-1); + private static final CellRangeAddress row2 = createCR( 2, 2, 0,-1); - private static final CellRange box0 = createCR( 0, 2, 0,2); - private static final CellRange box1 = createCR( 0, 1, 0,1); - private static final CellRange box2 = createCR( 0, 1, 2,3); - private static final CellRange box3 = createCR( 2, 3, 0,1); - private static final CellRange box4 = createCR( 2, 3, 2,3); - private static final CellRange box5 = createCR( 1, 3, 1,3); + private static final CellRangeAddress box0 = createCR( 0, 2, 0,2); + private static final CellRangeAddress box1 = createCR( 0, 1, 0,1); + private static final CellRangeAddress box2 = createCR( 0, 1, 2,3); + private static final CellRangeAddress box3 = createCR( 2, 3, 0,1); + private static final CellRangeAddress box4 = createCR( 2, 3, 2,3); + private static final CellRangeAddress box5 = createCR( 1, 3, 1,3); public void testHasSharedBorderMethod() { - assertFalse(col1.hasExactSharedBorder(col1)); - assertFalse(col2.hasExactSharedBorder(col2)); - assertTrue(col1.hasExactSharedBorder(col2)); - assertTrue(col2.hasExactSharedBorder(col1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col1, col1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col2, col2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(col1, col2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(col2, col1)); - assertFalse(row1.hasExactSharedBorder(row1)); - assertFalse(row2.hasExactSharedBorder(row2)); - assertTrue(row1.hasExactSharedBorder(row2)); - assertTrue(row2.hasExactSharedBorder(row1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row1, row1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row2, row2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(row1, row2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(row2, row1)); - assertFalse(row1.hasExactSharedBorder(col1)); - assertFalse(row1.hasExactSharedBorder(col2)); - assertFalse(col1.hasExactSharedBorder(row1)); - assertFalse(col2.hasExactSharedBorder(row1)); - assertFalse(row2.hasExactSharedBorder(col1)); - assertFalse(row2.hasExactSharedBorder(col2)); - assertFalse(col1.hasExactSharedBorder(row2)); - assertFalse(col2.hasExactSharedBorder(row2)); - assertTrue(col2.hasExactSharedBorder(col1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row1, col1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row1, col2)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col1, row1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col2, row1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row2, col1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(row2, col2)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col1, row2)); + assertFalse(CellRangeUtil.hasExactSharedBorder(col2, row2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(col2, col1)); - assertFalse(box1.hasExactSharedBorder(box1)); - assertTrue(box1.hasExactSharedBorder(box2)); - assertTrue(box1.hasExactSharedBorder(box3)); - assertFalse(box1.hasExactSharedBorder(box4)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box1, box1)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box1, box2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box1, box3)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box1, box4)); - assertTrue(box2.hasExactSharedBorder(box1)); - assertFalse(box2.hasExactSharedBorder(box2)); - assertFalse(box2.hasExactSharedBorder(box3)); - assertTrue(box2.hasExactSharedBorder(box4)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box2, box1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box2, box2)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box2, box3)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box2, box4)); - assertTrue(box3.hasExactSharedBorder(box1)); - assertFalse(box3.hasExactSharedBorder(box2)); - assertFalse(box3.hasExactSharedBorder(box3)); - assertTrue(box3.hasExactSharedBorder(box4)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box3, box1)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box3, box2)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box3, box3)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box3, box4)); - assertFalse(box4.hasExactSharedBorder(box1)); - assertTrue(box4.hasExactSharedBorder(box2)); - assertTrue(box4.hasExactSharedBorder(box3)); - assertFalse(box4.hasExactSharedBorder(box4)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box4, box1)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box4, box2)); + assertTrue(CellRangeUtil.hasExactSharedBorder(box4, box3)); + assertFalse(CellRangeUtil.hasExactSharedBorder(box4, box4)); } public void testIntersectMethod() { - assertEquals(CellRange.OVERLAP,box0.intersect(box5)); - assertEquals(CellRange.OVERLAP,box5.intersect(box0)); - assertEquals(CellRange.NO_INTERSECTION,box1.intersect(box4)); - assertEquals(CellRange.NO_INTERSECTION,box4.intersect(box1)); - assertEquals(CellRange.NO_INTERSECTION,box2.intersect(box3)); - assertEquals(CellRange.NO_INTERSECTION,box3.intersect(box2)); - assertEquals(CellRange.INSIDE,box0.intersect(box1)); - assertEquals(CellRange.INSIDE,box0.intersect(box0)); - assertEquals(CellRange.ENCLOSES,box1.intersect(box0)); - assertEquals(CellRange.INSIDE,tenthColumn.intersect(oneCell)); - assertEquals(CellRange.ENCLOSES,oneCell.intersect(tenthColumn)); - assertEquals(CellRange.OVERLAP,tenthColumn.intersect(tenthRow)); - assertEquals(CellRange.OVERLAP,tenthRow.intersect(tenthColumn)); - assertEquals(CellRange.INSIDE,tenthColumn.intersect(tenthColumn)); - assertEquals(CellRange.INSIDE,tenthRow.intersect(tenthRow)); + assertEquals(CellRangeUtil.OVERLAP, CellRangeUtil.intersect(box0, box5)); + assertEquals(CellRangeUtil.OVERLAP, CellRangeUtil.intersect(box5, box0)); + assertEquals(CellRangeUtil.NO_INTERSECTION, CellRangeUtil.intersect(box1, box4)); + assertEquals(CellRangeUtil.NO_INTERSECTION, CellRangeUtil.intersect(box4, box1)); + assertEquals(CellRangeUtil.NO_INTERSECTION, CellRangeUtil.intersect(box2, box3)); + assertEquals(CellRangeUtil.NO_INTERSECTION, CellRangeUtil.intersect(box3, box2)); + assertEquals(CellRangeUtil.INSIDE, CellRangeUtil.intersect(box0, box1)); + assertEquals(CellRangeUtil.INSIDE, CellRangeUtil.intersect(box0, box0)); + assertEquals(CellRangeUtil.ENCLOSES, CellRangeUtil.intersect(box1, box0)); + assertEquals(CellRangeUtil.INSIDE, CellRangeUtil.intersect(tenthColumn, oneCell)); + assertEquals(CellRangeUtil.ENCLOSES, CellRangeUtil.intersect(oneCell, tenthColumn)); + assertEquals(CellRangeUtil.OVERLAP, CellRangeUtil.intersect(tenthColumn, tenthRow)); + assertEquals(CellRangeUtil.OVERLAP, CellRangeUtil.intersect(tenthRow, tenthColumn)); + assertEquals(CellRangeUtil.INSIDE, CellRangeUtil.intersect(tenthColumn, tenthColumn)); + assertEquals(CellRangeUtil.INSIDE, CellRangeUtil.intersect(tenthRow, tenthRow)); } /** @@ -155,7 +157,7 @@ public final class TestCellRange extends TestCase * =$C:$IV,$B$1:$B$8,$B$10:$B$65536,$A:$A */ public void testCreate() { - CellRange cr; + CellRangeAddress cr; cr = createCR(0, -1, 2, 255); // $C:$IV confirmRange(cr, false, true); @@ -172,7 +174,7 @@ public final class TestCellRange extends TestCase cr = createCR(0, -1, 0, 0); // $A:$A } - private static void confirmRange(CellRange cr, boolean isFullRow, boolean isFullColumn) { + private static void confirmRange(CellRangeAddress cr, boolean isFullRow, boolean isFullColumn) { assertEquals("isFullRowRange", isFullRow, cr.isFullRowRange()); assertEquals("isFullColumnRange", isFullColumn, cr.isFullColumnRange()); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/StreamUtility.java b/src/testcases/org/apache/poi/hssf/usermodel/StreamUtility.java new file mode 100644 index 000000000..45ae4ac36 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/StreamUtility.java @@ -0,0 +1,131 @@ +/* ==================================================================== + 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.usermodel; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class to help test code verify that generated files do not differ from proof copies in + * any significant detail. Normally this task would be simple except for the presence of artifacts + * in the file that change every time it is generated. Usually these volatile artifacts are + * time-stamps, user names, or other machine dependent parameters. + * + * @author Josh Micich + */ +public final class StreamUtility { + + /** + * Compares two streams with expected differences in specified regions. The streams are + * expected to be of equal length and comparison is always byte for byte. That is - + * differences can only involve exchanging each individual byte for another single byte.
    + * Both input streams are closed. + * + * @param allowableDifferenceRegions array of integer pairs: (offset, length). + * Any differences encountered in these regions of the streams will be ignored + * @return null if streams are identical, else the + * byte indexes of differing data. If streams were different lengths, + * the returned indexes will be -1 and the length of the shorter stream + */ + public static int[] diffStreams(InputStream isA, InputStream isB, int[] allowableDifferenceRegions) { + + if((allowableDifferenceRegions.length % 2) != 0) { + throw new RuntimeException("allowableDifferenceRegions length is odd"); + } + boolean success = false; + int[] result; + try { + result = diffInternal(isA, isB, allowableDifferenceRegions); + success = true; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + close(isA, success); + close(isB, success); + } + return result; + } + + /** + * @param success false if the outer method is throwing an exception. + */ + private static void close(InputStream is, boolean success) { + try { + is.close(); + } catch (IOException e) { + if(success) { + // this is a new error. ok to throw + throw new RuntimeException(e); + } + // else don't subvert original exception. just print stack trace for this one + e.printStackTrace(); + } + } + + private static int[] diffInternal(InputStream isA, InputStream isB, int[] allowableDifferenceRegions) + throws IOException { + int offset = 0; + List temp = new ArrayList(); + while (true) { + int b = isA.read(); + int b2 = isB.read(); + if (b == -1) { + // EOF + if (b2 == -1) { + return toPrimitiveIntArray(temp); + } + return new int[] { -1, offset, }; + } + if (b2 == -1) { + return new int[] { -1, offset, }; + } + if (b != b2 && !isIgnoredRegion(allowableDifferenceRegions, offset)) { + temp.add(new Integer(offset)); + } + offset++; + } + } + + private static boolean isIgnoredRegion(int[] allowableDifferenceRegions, int offset) { + for (int i = 0; i < allowableDifferenceRegions.length; i+=2) { + int start = allowableDifferenceRegions[i]; + int end = start + allowableDifferenceRegions[i+1]; + if(start <= offset && offset < end) { + return true; + } + } + return false; + } + + private static int[] toPrimitiveIntArray(List temp) { + int nItems = temp.size(); + if(nItems < 1) { + return null; + } + Integer[] boxInts = new Integer[nItems]; + temp.toArray(boxInts); + + int[] result = new int[nItems]; + for (int i = 0; i < result.length; i++) { + result[i] = boxInts[i].intValue(); + } + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 69937d103..541fb893a 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -28,6 +28,7 @@ import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.ss.util.Region; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.model.Workbook; @@ -302,15 +303,14 @@ public final class TestBugs extends TestCase { /** * Merged regions were being removed from the parent in cloned sheets - * @throws Exception */ public void test22720() { HSSFWorkbook workBook = new HSSFWorkbook(); workBook.createSheet("TEST"); HSSFSheet template = workBook.getSheetAt(0); - template.addMergedRegion(new Region(0, (short)0, 1, (short)2)); - template.addMergedRegion(new Region(1, (short)0, 2, (short)2)); + template.addMergedRegion(new CellRangeAddress(0, 1, 0, 2)); + template.addMergedRegion(new CellRangeAddress(1, 2, 0, 2)); HSSFSheet clone = workBook.cloneSheet(0); int originalMerged = template.getNumMergedRegions(); @@ -318,20 +318,20 @@ public final class TestBugs extends TestCase { // remove merged regions from clone for (int i=template.getNumMergedRegions()-1; i>=0; i--) { - clone.removeMergedRegion(i); + clone.removeMergedRegion(i); } assertEquals("Original Sheet's Merged Regions were removed", originalMerged, template.getNumMergedRegions()); // check if template's merged regions are OK if (template.getNumMergedRegions()>0) { - // fetch the first merged region...EXCEPTION OCCURS HERE - template.getMergedRegionAt(0); + // fetch the first merged region...EXCEPTION OCCURS HERE + template.getMergedRegion(0); } //make sure we dont exception } - /*Tests read and write of Unicode strings in formula results + /**Tests read and write of Unicode strings in formula results * bug and testcase submitted by Sompop Kumnoonsate * The file contains THAI unicode characters. */ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java index 6a0b15158..4704b3ed8 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java @@ -21,6 +21,7 @@ package org.apache.poi.hssf.usermodel; import junit.framework.TestCase; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.Region; /** @@ -29,23 +30,15 @@ import org.apache.poi.ss.util.Region; * add that record to the sheet in the testCloneSheetBasic method. * @author avik */ -public class TestCloneSheet extends TestCase { +public final class TestCloneSheet extends TestCase { - public TestCloneSheet(String arg0) { - super(arg0); - } - public void testCloneSheetBasic(){ - try{ - HSSFWorkbook b = new HSSFWorkbook(); - HSSFSheet s = b.createSheet("Test"); - s.addMergedRegion(new Region((short)0,(short)0,(short)1,(short)1)); - HSSFSheet clonedSheet = b.cloneSheet(0); - - assertEquals("One merged area", 1, clonedSheet.getNumMergedRegions()); - - } - catch(Exception e){e.printStackTrace();fail(e.getMessage());} + HSSFWorkbook b = new HSSFWorkbook(); + HSSFSheet s = b.createSheet("Test"); + s.addMergedRegion(new CellRangeAddress(0, 1, 0, 1)); + HSSFSheet clonedSheet = b.cloneSheet(0); + + assertEquals("One merged area", 1, clonedSheet.getNumMergedRegions()); } /** @@ -66,5 +59,4 @@ public class TestCloneSheet extends TestCase { assertTrue("Row 3 still should be broken", clone.isRowBroken(3)); } - } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java index 2c28edf3a..91382af06 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java @@ -16,896 +16,631 @@ package org.apache.poi.hssf.usermodel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.apache.poi.hssf.util.*; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.eventmodel.ERFListener; +import org.apache.poi.hssf.eventmodel.EventRecordFactory; +import org.apache.poi.hssf.record.DVRecord; +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.util.Region; -import java.io.*; -import java.util.*; -import java.text.SimpleDateFormat; - /** - *

    Title: TestDataValidation

    - *

    Description: Class for testing Excel's data validation mechanism - * Second test : - * - - *

    + * Class for testing Excel's data validation mechanism + * * @author Dragos Buleandra ( dragos.buleandra@trade2b.ro ) */ -public class TestDataValidation extends TestCase -{ - public TestDataValidation(String name) - { - super(name); - } - - protected void setUp() - { - String filename = System.getProperty("HSSF.testdata.path"); - if (filename == null) - { - System.setProperty("HSSF.testdata.path", "src/testcases/org/apache/poi/hssf/data"); - } - } - - public void testDataValidation() throws Exception - { - System.out.println("\nTest no. 2 - Test Excel's Data validation mechanism"); - String resultFile = System.getProperty("java.io.tmpdir")+File.separator+"TestDataValidation.xls"; - HSSFWorkbook wb = new HSSFWorkbook(); - - HSSFCellStyle style_1 = this.createStyle( wb, HSSFCellStyle.ALIGN_LEFT ); - HSSFCellStyle style_2 = this.createStyle( wb, HSSFCellStyle.ALIGN_CENTER ); - HSSFCellStyle style_3 = this.createStyle( wb, HSSFCellStyle.ALIGN_CENTER, HSSFColor.GREY_25_PERCENT.index, true ); - HSSFCellStyle style_4 = this.createHeaderStyle(wb); - HSSFDataValidation data_validation = null; - - //data validation's number types - System.out.print(" Create sheet for Data Validation's number types ... "); - HSSFSheet fSheet = wb.createSheet("Number types"); - - //"Whole number" validation type - this.createDVTypeRow( wb, 0, style_3, "Whole number"); - this.createHeaderRow( wb, 0, style_4 ); - - short start_row = (short)fSheet.getPhysicalNumberOfRows(); - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_INTEGER); - data_validation.setOperator(HSSFDataValidation.OPERATOR_BETWEEN); - data_validation.setFirstFormula("2"); - data_validation.setSecondFormula("6"); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Between 2 and 6 ", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+1)); - data_validation.setLastRow((short)(start_row+1)); - data_validation.setEmptyCellAllowed(false); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_BETWEEN); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_INFO); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not between 2 and 6 ", false, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = INFO" ); - - data_validation.setFirstRow((short)(start_row+2)); - data_validation.setLastRow((short)(start_row+2)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_EQUAL); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Equal to 3", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - data_validation.setFirstRow((short)(start_row+3)); - data_validation.setLastRow((short)(start_row+3)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not equal to 3", false, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+4)); - data_validation.setLastRow((short)(start_row+4)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than 3", true, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+5)); - data_validation.setLastRow((short)(start_row+5)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than 3", true, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+6)); - data_validation.setLastRow((short)(start_row+6)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_STOP); - data_validation.setShowErrorBox(true); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than or equal to 4", true, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+7)); - data_validation.setLastRow((short)(start_row+7)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than or equal to 4", false, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - //"Decimal" validation type - this.createDVTypeRow( wb, 0, style_3, "Decimal"); - this.createHeaderRow( wb, 0, style_4 ); - - start_row += (short)(8+4); - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_DECIMAL); - data_validation.setOperator(HSSFDataValidation.OPERATOR_BETWEEN); - data_validation.setFirstFormula("2"); - data_validation.setSecondFormula("6"); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Between 2 and 6 ", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+1)); - data_validation.setLastRow((short)(start_row+1)); - data_validation.setEmptyCellAllowed(false); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_BETWEEN); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_INFO); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not between 2 and 6 ", false, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = INFO" ); - - data_validation.setFirstRow((short)(start_row+2)); - data_validation.setLastRow((short)(start_row+2)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_EQUAL); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Equal to 3", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - data_validation.setFirstRow((short)(start_row+3)); - data_validation.setLastRow((short)(start_row+3)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not equal to 3", false, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+4)); - data_validation.setLastRow((short)(start_row+4)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than 3", true, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+5)); - data_validation.setLastRow((short)(start_row+5)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than 3", true, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+6)); - data_validation.setLastRow((short)(start_row+6)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_STOP); - data_validation.setShowErrorBox(true); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than or equal to 4", true, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+7)); - data_validation.setLastRow((short)(start_row+7)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than or equal to 4", false, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - System.out.println("done !"); - - //"List" Data Validation type - /** @todo List*/ - System.out.print(" Create sheet for 'List' Data Validation type ... "); - fSheet = wb.createSheet("Lists"); - - this.createDVTypeRow( wb, 1, style_3, "Explicit lists - list items are explicitly provided"); - this.createDVDeescriptionRow( wb, 1, style_3, "Disadvantage - sum of item's length should be less than 255 characters"); - this.createHeaderRow( wb, 1, style_4 ); - - start_row = (short)fSheet.getPhysicalNumberOfRows(); - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula("1+2+3"); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(false); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "POIFS,HSSF,HWPF,HPSF", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=yes" ); - - data_validation = new HSSFDataValidation((short)(start_row+1),(short)0,(short)(start_row+1),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula("4+5+6+7"); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(false); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "POIFS,HSSF,HWPF,HPSF", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=yes" ); - - data_validation = new HSSFDataValidation((short)(start_row+2),(short)0,(short)(start_row+2),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula("7+21"); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(true); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "POIFS,HSSF,HWPF,HPSF", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=no" ); - - data_validation = new HSSFDataValidation((short)(start_row+3),(short)0,(short)(start_row+3),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula("8/2"); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(true); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "POIFS,HSSF,HWPF,HPSF", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=no" ); - - this.createDVTypeRow( wb, 1, style_3, "Reference lists - list items are taken from others cells"); - this.createDVDeescriptionRow( wb, 1, style_3, "Advantage - no restriction regarding the sum of item's length"); - this.createHeaderRow( wb, 1, style_4 ); - - start_row += (short)(4+5); - String cellStrValue = "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 "+ - "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 "+ - "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 "+ - "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 "; - - String strFormula = "$A$100:$A$120"; - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula(strFormula); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(false); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, strFormula, true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=yes" ); - - data_validation = new HSSFDataValidation((short)(start_row+1),(short)0,(short)(start_row+1),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula(strFormula); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(false); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, strFormula, false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=yes" ); - - data_validation = new HSSFDataValidation((short)(start_row+2),(short)0,(short)(start_row+2),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula(strFormula); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(true); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, strFormula, true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=no" ); - - data_validation = new HSSFDataValidation((short)(start_row+3),(short)0,(short)(start_row+3),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_LIST); - data_validation.setFirstFormula(strFormula); - data_validation.setSecondFormula(null); - data_validation.setSurppressDropDownArrow(true); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, strFormula, false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type=STOP ; In-cell dropdown=no" ); - - for (int i=100; i<=120; i++) - { - HSSFRow currRow = fSheet.createRow(i); - currRow.createCell((short)0).setCellValue(cellStrValue); -// currRow.hide( true ); - } - - System.out.println("done !"); - - //Date/Time Validation type - System.out.print(" Create sheet for 'Date' and 'Time' Data Validation types ... "); - fSheet = wb.createSheet("Date_Time"); - SimpleDateFormat df = new SimpleDateFormat("m/d/yyyy"); - HSSFDataFormat dataFormat = wb.createDataFormat(); - short fmtDate = dataFormat.getFormat("m/d/yyyy"); - short fmtTime = dataFormat.getFormat("h:mm"); - HSSFCellStyle cellStyle_data = wb.createCellStyle(); - cellStyle_data.setDataFormat(fmtDate); - HSSFCellStyle cellStyle_time = wb.createCellStyle(); - cellStyle_time.setDataFormat(fmtTime); - - this.createDVTypeRow( wb, 2, style_3, "Date ( cells are already formated as date - m/d/yyyy)"); - this.createHeaderRow( wb, 2, style_4 ); - - start_row = (short)fSheet.getPhysicalNumberOfRows(); - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_DATE); - data_validation.setOperator(HSSFDataValidation.OPERATOR_BETWEEN); - - data_validation.setFirstFormula( String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("1/2/2004"))) ); - data_validation.setSecondFormula( String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("1/6/2004"))) ); - - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Between 1/2/2004 and 1/6/2004 ", true, true, true ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+1)); - data_validation.setLastRow((short)(start_row+1)); - data_validation.setEmptyCellAllowed(false); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_BETWEEN); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_INFO); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not between 1/2/2004 and 1/6/2004 ", false, true, true ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "Error box type = INFO" ); - - data_validation.setFirstRow((short)(start_row+2)); - data_validation.setLastRow((short)(start_row+2)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/2/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_EQUAL); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Equal to 3/2/2004", false, false, true ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - data_validation.setFirstRow((short)(start_row+3)); - data_validation.setLastRow((short)(start_row+3)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/2/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not equal to 3/2/2004", false, false, false ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+4)); - data_validation.setLastRow((short)(start_row+4)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/2/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than 3/2/2004", true, false, false ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+5)); - data_validation.setLastRow((short)(start_row+5)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/2/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than 3/2/2004", true, true, false ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+6)); - data_validation.setLastRow((short)(start_row+6)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_STOP); - data_validation.setShowErrorBox(true); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/2/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than or equal to 3/2/2004", true, false, true ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+7)); - data_validation.setLastRow((short)(start_row+7)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("3/4/2004")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than or equal to 3/4/2004", false, true, false ); - this.setCellFormat( fSheet, cellStyle_data ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - //"Time" validation type - this.createDVTypeRow( wb, 2, style_3, "Time ( cells are already formated as time - h:mm)"); - this.createHeaderRow( wb, 2, style_4 ); - - df = new SimpleDateFormat("hh:mm"); - - start_row += (short)(8+4); - data_validation = new HSSFDataValidation((short)(start_row),(short)0,(short)(start_row),(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_TIME); - data_validation.setOperator(HSSFDataValidation.OPERATOR_BETWEEN); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("12:00")))); - data_validation.setSecondFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("16:00")))); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Between 12:00 and 16:00 ", true, true, true ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+1)); - data_validation.setLastRow((short)(start_row+1)); - data_validation.setEmptyCellAllowed(false); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_BETWEEN); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_INFO); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not between 12:00 and 16:00 ", false, true, true ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "Error box type = INFO" ); - - data_validation.setFirstRow((short)(start_row+2)); - data_validation.setLastRow((short)(start_row+2)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setFirstFormula(String.valueOf((int)HSSFDateUtil.getExcelDate(df.parse("13:35")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_EQUAL); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Equal to 13:35", false, false, true ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - data_validation.setFirstRow((short)(start_row+3)); - data_validation.setLastRow((short)(start_row+3)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("13:35")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not equal to 13:35", false, false, false ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+4)); - data_validation.setLastRow((short)(start_row+4)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("12:00")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than 12:00", true, false, false ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+5)); - data_validation.setLastRow((short)(start_row+5)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("12:00")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than 12:00", true, true, false ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)(start_row+6)); - data_validation.setLastRow((short)(start_row+6)); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_STOP); - data_validation.setShowErrorBox(true); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("14:00")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than or equal to 14:00", true, false, true ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)(start_row+7)); - data_validation.setLastRow((short)(start_row+7)); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula(String.valueOf(HSSFDateUtil.getExcelDate(df.parse("14:00")))); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than or equal to 14:00", false, true, false ); - this.setCellFormat( fSheet, cellStyle_time ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - System.out.println("done !"); - - //"Text length" validation type - System.out.print(" Create sheet for 'Text length' Data Validation type... "); - fSheet = wb.createSheet("Text length"); - this.createHeaderRow( wb, 3, style_4 ); - - data_validation = new HSSFDataValidation((short)1,(short)0,(short)1,(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_TEXT_LENGTH); - data_validation.setOperator(HSSFDataValidation.OPERATOR_BETWEEN); - data_validation.setFirstFormula("2"); - data_validation.setSecondFormula("6"); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Between 2 and 6 ", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)2); - data_validation.setLastRow((short)2); - data_validation.setEmptyCellAllowed(false); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_BETWEEN); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_INFO); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not between 2 and 6 ", false, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = INFO" ); - - data_validation.setFirstRow((short)3); - data_validation.setLastRow((short)3); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_EQUAL); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Equal to 3", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - data_validation.setFirstRow((short)4); - data_validation.setLastRow((short)4); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_NOT_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Not equal to 3", false, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)5); - data_validation.setLastRow((short)5); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than 3", true, false, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)6); - data_validation.setLastRow((short)6); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("3"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_THAN); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than 3", true, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - - data_validation.setFirstRow((short)7); - data_validation.setLastRow((short)7); - data_validation.setEmptyCellAllowed(true); - data_validation.setShowPromptBox(false); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_STOP); - data_validation.setShowErrorBox(true); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_GREATER_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Greater than or equal to 4", true, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation.setFirstRow((short)8); - data_validation.setLastRow((short)8); - data_validation.setEmptyCellAllowed(false); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(false); - data_validation.setFirstFormula("4"); - data_validation.setSecondFormula(null); - data_validation.setOperator(HSSFDataValidation.OPERATOR_LESS_OR_EQUAL); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "Less than or equal to 4", false, true, false ); - this.writeOtherSettings( fSheet, style_1, "-" ); - System.out.println("done !"); - - //Custom Validation type - System.out.print(" Create sheet for 'Custom' Data Validation type ... "); - fSheet = wb.createSheet("Custom"); - this.createHeaderRow( wb, 4, style_4 ); - - data_validation = new HSSFDataValidation((short)1,(short)0,(short)1,(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_FORMULA); - data_validation.setFirstFormula("ISNUMBER($A2)"); - data_validation.setSecondFormula(null); - data_validation.setShowPromptBox(true); - data_validation.setShowErrorBox(true); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.createPromptBox("Hi , dear user !", "So , you just selected me ! Thanks !"); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "ISNUMBER(A2)", true, true, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = STOP" ); - - data_validation = new HSSFDataValidation((short)2,(short)0,(short)2,(short)0); - data_validation.setDataValidationType(HSSFDataValidation.DATA_TYPE_FORMULA); - data_validation.setFirstFormula("IF(SUM(A2:A3)=5,TRUE,FALSE)"); - data_validation.setSecondFormula(null); - data_validation.setShowPromptBox(false); - data_validation.setShowErrorBox(true); - data_validation.setErrorStyle(HSSFDataValidation.ERROR_STYLE_WARNING); - data_validation.createErrorBox("Invalid input !", "Something is wrong ; check condition !"); - data_validation.setEmptyCellAllowed(false); - fSheet.addValidationData(data_validation); - this.writeDataValidationSettings( fSheet, style_1, style_2, "IF(SUM(A2:A3)=5,TRUE,FALSE)", false, false, true ); - this.writeOtherSettings( fSheet, style_1, "Error box type = WARNING" ); - - System.out.println("done !"); - - //so , everything it's ok for now ; it remains for you to open the file - System.out.println("\n Everything it's ok since we've got so far -:) !\n"+ - " In order to complete the test , it remains for you to open the file \n"+ - " and see if there are four sheets , as described !"); - System.out.println(" File was saved in \""+resultFile+"\""); - - FileOutputStream fileOut = new FileOutputStream(resultFile); - wb.write(fileOut); - fileOut.close(); - } - - private void createDVTypeRow( HSSFWorkbook wb, int sheetNo , HSSFCellStyle cellStyle, String strTypeDescription) - { - HSSFSheet sheet = wb.getSheetAt(sheetNo); - HSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows()); - row = sheet.createRow(sheet.getPhysicalNumberOfRows()); - sheet.addMergedRegion(new Region((short)(sheet.getPhysicalNumberOfRows()-1),(short)0,(short)(sheet.getPhysicalNumberOfRows()-1),(short)5)); - HSSFCell cell = row.createCell((short)0); - cell.setCellValue(strTypeDescription); - cell.setCellStyle(cellStyle); - row = sheet.createRow(sheet.getPhysicalNumberOfRows()); - } - - private void createDVDeescriptionRow( HSSFWorkbook wb, int sheetNo , HSSFCellStyle cellStyle, String strTypeDescription ) - { - HSSFSheet sheet = wb.getSheetAt(sheetNo); - HSSFRow row = sheet.getRow(sheet.getPhysicalNumberOfRows()-1); - sheet.addMergedRegion(new Region((short)(sheet.getPhysicalNumberOfRows()-1),(short)0,(short)(sheet.getPhysicalNumberOfRows()-1),(short)5)); - HSSFCell cell = row.createCell((short)0); - cell.setCellValue(strTypeDescription); - cell.setCellStyle(cellStyle); - row = sheet.createRow(sheet.getPhysicalNumberOfRows()); - } - - private void createHeaderRow( HSSFWorkbook wb, int sheetNo , HSSFCellStyle cellStyle ) - { - HSSFSheet sheet = wb.getSheetAt(sheetNo); - HSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows()); - row.setHeight((short)400); - for ( int i=0; i<6; i++ ) - { - row.createCell((short)i).setCellStyle( cellStyle ); - if ( i==2 || i==3 || i==4 ) - { - sheet.setColumnWidth( (short) i, (short) 3500); - } - else if ( i== 5) - { - sheet.setColumnWidth( (short) i, (short) 10000); - } - else - { - sheet.setColumnWidth( (short) i, (short) 8000); - } - } - HSSFCell cell = row.getCell((short)0); - cell.setCellValue("Data validation cells"); - cell = row.getCell((short)1); - cell.setCellValue("Condition"); - cell = row.getCell((short)2); - cell.setCellValue("Allow blank"); - cell = row.getCell((short)3); - cell.setCellValue("Prompt box"); - cell = row.getCell((short)4); - cell.setCellValue("Error box"); - cell = row.getCell((short)5); - cell.setCellValue("Other settings"); - } - - private HSSFCellStyle createHeaderStyle(HSSFWorkbook wb) - { - HSSFFont font = wb.createFont(); - font.setColor( HSSFColor.WHITE.index ); - font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); - - HSSFCellStyle cellStyle = wb.createCellStyle(); - cellStyle.setFillForegroundColor(HSSFColor.BLUE_GREY.index); - cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); - cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); - cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); - cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); - cellStyle.setLeftBorderColor(HSSFColor.WHITE.index); - cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); - cellStyle.setTopBorderColor(HSSFColor.WHITE.index); - cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); - cellStyle.setRightBorderColor(HSSFColor.WHITE.index); - cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); - cellStyle.setBottomBorderColor(HSSFColor.WHITE.index); - cellStyle.setFont(font); - return cellStyle; - } - - private HSSFCellStyle createStyle( HSSFWorkbook wb, short h_align, short color, boolean bold ) - { - HSSFFont font = wb.createFont(); - if ( bold ) - { - font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); - } - - HSSFCellStyle cellStyle = wb.createCellStyle(); - cellStyle.setFont(font); - cellStyle.setFillForegroundColor(color); - cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); - cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); - cellStyle.setAlignment(h_align); - cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); - cellStyle.setLeftBorderColor(HSSFColor.BLACK.index); - cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); - cellStyle.setTopBorderColor(HSSFColor.BLACK.index); - cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); - cellStyle.setRightBorderColor(HSSFColor.BLACK.index); - cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); - cellStyle.setBottomBorderColor(HSSFColor.BLACK.index); - - return cellStyle; - } - - private HSSFCellStyle createStyle( HSSFWorkbook wb, short h_align ) - { - return this.createStyle(wb, h_align, HSSFColor.WHITE.index, false); - } - - private void writeDataValidationSettings( HSSFSheet sheet, HSSFCellStyle style_1, HSSFCellStyle style_2, String strCondition, boolean allowEmpty, boolean inputBox, boolean errorBox ) - { - HSSFRow row = sheet.createRow( sheet.getPhysicalNumberOfRows() ); - //condition's string - HSSFCell cell = row.createCell((short)1); - cell.setCellStyle(style_1); - cell.setCellValue(strCondition); - //allow empty cells - cell = row.createCell((short)2); - cell.setCellStyle(style_2); - cell.setCellValue( ((allowEmpty) ? "yes" : "no") ); - //show input box - cell = row.createCell((short)3); - cell.setCellStyle(style_2); - cell.setCellValue( ((inputBox) ? "yes" : "no") ); - //show error box - cell = row.createCell((short)4); - cell.setCellStyle(style_2); - cell.setCellValue( ((errorBox) ? "yes" : "no") ); - } - - private void setCellFormat( HSSFSheet sheet, HSSFCellStyle cell_style ) - { - HSSFRow row = sheet.getRow( sheet.getPhysicalNumberOfRows() -1 ); - HSSFCell cell = row.createCell((short)0); - cell.setCellStyle(cell_style); - } - - private void writeOtherSettings( HSSFSheet sheet, HSSFCellStyle style, String strStettings ) - { - HSSFRow row = sheet.getRow( sheet.getPhysicalNumberOfRows() -1 ); - HSSFCell cell = row.createCell((short)5); - cell.setCellStyle(style); - cell.setCellValue(strStettings); - } - - public static void main(String[] args) - { - junit.textui.TestRunner.run(TestDataValidation.class); +public final class TestDataValidation extends TestCase { + + /** Convenient access to ERROR_STYLE constants */ + /*package*/ static final HSSFDataValidation.ErrorStyle ES = null; + /** Convenient access to OPERATOR constants */ + /*package*/ static final DVConstraint.ValidationType VT = null; + /** Convenient access to OPERATOR constants */ + /*package*/ static final DVConstraint.OperatorType OP = null; + + private static void log(String msg) { + if (false) { // successful tests should be silent + System.out.println(msg); + } + } + + private static final class ValidationAdder { + + private final HSSFCellStyle _style_1; + private final HSSFCellStyle _style_2; + private final int _validationType; + private final HSSFSheet _sheet; + private int _currentRowIndex; + private final HSSFCellStyle _cellStyle; + + public ValidationAdder(HSSFSheet fSheet, HSSFCellStyle style_1, HSSFCellStyle style_2, + HSSFCellStyle cellStyle, int validationType) { + _sheet = fSheet; + _style_1 = style_1; + _style_2 = style_2; + _cellStyle = cellStyle; + _validationType = validationType; + _currentRowIndex = fSheet.getPhysicalNumberOfRows(); + } + public void addValidation(int operatorType, String firstFormula, String secondFormula, + int errorStyle, String ruleDescr, String promptDescr, + boolean allowEmpty, boolean inputBox, boolean errorBox) { + String[] explicitListValues = null; + + addValidationInternal(operatorType, firstFormula, secondFormula, errorStyle, ruleDescr, + promptDescr, allowEmpty, inputBox, errorBox, true, + explicitListValues); + } + + private void addValidationInternal(int operatorType, String firstFormula, + String secondFormula, int errorStyle, String ruleDescr, String promptDescr, + boolean allowEmpty, boolean inputBox, boolean errorBox, boolean suppressDropDown, + String[] explicitListValues) { + int rowNum = _currentRowIndex++; + + DVConstraint dc = createConstraint(operatorType, firstFormula, secondFormula, explicitListValues); + + HSSFDataValidation dv = new HSSFDataValidation(new CellRangeAddressList(rowNum, rowNum, 0, 0), dc); + + dv.setEmptyCellAllowed(allowEmpty); + dv.setErrorStyle(errorStyle); + dv.createErrorBox("Invalid Input", "Something is wrong - check condition!"); + dv.createPromptBox("Validated Cell", "Allowable values have been restricted"); + + dv.setShowPromptBox(inputBox); + dv.setShowErrorBox(errorBox); + dv.setSuppressDropDownArrow(suppressDropDown); + + + _sheet.addValidationData(dv); + writeDataValidationSettings(_sheet, _style_1, _style_2, ruleDescr, allowEmpty, + inputBox, errorBox); + if (_cellStyle != null) { + HSSFRow row = _sheet.getRow(_sheet.getPhysicalNumberOfRows() - 1); + HSSFCell cell = row.createCell((short) 0); + cell.setCellStyle(_cellStyle); + } + writeOtherSettings(_sheet, _style_1, promptDescr); + } + private DVConstraint createConstraint(int operatorType, String firstFormula, + String secondFormula, String[] explicitListValues) { + if (_validationType == VT.LIST) { + if (explicitListValues != null) { + return DVConstraint.createExplicitListConstraint(explicitListValues); + } + return DVConstraint.createFormulaListConstraint(firstFormula); + } + if (_validationType == VT.TIME) { + return DVConstraint.createTimeConstraint(operatorType, firstFormula, secondFormula); + } + if (_validationType == VT.DATE) { + return DVConstraint.createDateConstraint(operatorType, firstFormula, secondFormula, null); + } + if (_validationType == VT.FORMULA) { + return DVConstraint.createCustomFormulaConstraint(firstFormula); + } + return DVConstraint.createNumericConstraint(_validationType, operatorType, firstFormula, secondFormula); + } + /** + * writes plain text values into cells in a tabular format to form comments readable from within + * the spreadsheet. + */ + private static void writeDataValidationSettings(HSSFSheet sheet, HSSFCellStyle style_1, + HSSFCellStyle style_2, String strCondition, boolean allowEmpty, boolean inputBox, + boolean errorBox) { + HSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + // condition's string + HSSFCell cell = row.createCell((short) 1); + cell.setCellStyle(style_1); + setCellValue(cell, strCondition); + // allow empty cells + cell = row.createCell((short) 2); + cell.setCellStyle(style_2); + setCellValue(cell, ((allowEmpty) ? "yes" : "no")); + // show input box + cell = row.createCell((short) 3); + cell.setCellStyle(style_2); + setCellValue(cell, ((inputBox) ? "yes" : "no")); + // show error box + cell = row.createCell((short) 4); + cell.setCellStyle(style_2); + setCellValue(cell, ((errorBox) ? "yes" : "no")); + } + private static void writeOtherSettings(HSSFSheet sheet, HSSFCellStyle style, + String strStettings) { + HSSFRow row = sheet.getRow(sheet.getPhysicalNumberOfRows() - 1); + HSSFCell cell = row.createCell((short) 5); + cell.setCellStyle(style); + setCellValue(cell, strStettings); + } + public void addListValidation(String[] explicitListValues, String listFormula, String listValsDescr, + boolean allowEmpty, boolean suppressDropDown) { + String promptDescr = (allowEmpty ? "empty ok" : "not empty") + + ", " + (suppressDropDown ? "no drop-down" : "drop-down"); + addValidationInternal(VT.LIST, listFormula, null, ES.STOP, listValsDescr, promptDescr, + allowEmpty, false, true, suppressDropDown, explicitListValues); + } + } + + /** + * Manages the cell styles used for formatting the output spreadsheet + */ + private static final class WorkbookFormatter { + + private final HSSFWorkbook _wb; + private final HSSFCellStyle _style_1; + private final HSSFCellStyle _style_2; + private final HSSFCellStyle _style_3; + private final HSSFCellStyle _style_4; + private HSSFSheet _currentSheet; + + public WorkbookFormatter(HSSFWorkbook wb) { + _wb = wb; + _style_1 = createStyle( wb, HSSFCellStyle.ALIGN_LEFT ); + _style_2 = createStyle( wb, HSSFCellStyle.ALIGN_CENTER ); + _style_3 = createStyle( wb, HSSFCellStyle.ALIGN_CENTER, HSSFColor.GREY_25_PERCENT.index, true ); + _style_4 = createHeaderStyle(wb); + } + + private static HSSFCellStyle createStyle(HSSFWorkbook wb, short h_align, short color, + boolean bold) { + HSSFFont font = wb.createFont(); + if (bold) { + font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); + } + + HSSFCellStyle cellStyle = wb.createCellStyle(); + cellStyle.setFont(font); + cellStyle.setFillForegroundColor(color); + cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); + cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); + cellStyle.setAlignment(h_align); + cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); + cellStyle.setLeftBorderColor(HSSFColor.BLACK.index); + cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); + cellStyle.setTopBorderColor(HSSFColor.BLACK.index); + cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); + cellStyle.setRightBorderColor(HSSFColor.BLACK.index); + cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); + cellStyle.setBottomBorderColor(HSSFColor.BLACK.index); + + return cellStyle; + } + + private static HSSFCellStyle createStyle(HSSFWorkbook wb, short h_align) { + return createStyle(wb, h_align, HSSFColor.WHITE.index, false); + } + private static HSSFCellStyle createHeaderStyle(HSSFWorkbook wb) { + HSSFFont font = wb.createFont(); + font.setColor( HSSFColor.WHITE.index ); + font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); + + HSSFCellStyle cellStyle = wb.createCellStyle(); + cellStyle.setFillForegroundColor(HSSFColor.BLUE_GREY.index); + cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); + cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); + cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); + cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN); + cellStyle.setLeftBorderColor(HSSFColor.WHITE.index); + cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN); + cellStyle.setTopBorderColor(HSSFColor.WHITE.index); + cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN); + cellStyle.setRightBorderColor(HSSFColor.WHITE.index); + cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); + cellStyle.setBottomBorderColor(HSSFColor.WHITE.index); + cellStyle.setFont(font); + return cellStyle; + } + + + public HSSFSheet createSheet(String sheetName) { + _currentSheet = _wb.createSheet(sheetName); + return _currentSheet; + } + public void createDVTypeRow(String strTypeDescription) { + HSSFSheet sheet = _currentSheet; + HSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + sheet.addMergedRegion(new CellRangeAddress(sheet.getPhysicalNumberOfRows()-1, sheet.getPhysicalNumberOfRows()-1, 0, 5)); + HSSFCell cell = row.createCell((short) 0); + setCellValue(cell, strTypeDescription); + cell.setCellStyle(_style_3); + row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + } + + public void createHeaderRow() { + HSSFSheet sheet = _currentSheet; + HSSFRow row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + row.setHeight((short) 400); + for (int i = 0; i < 6; i++) { + row.createCell((short) i).setCellStyle(_style_4); + if (i == 2 || i == 3 || i == 4) { + sheet.setColumnWidth((short) i, (short) 3500); + } else if (i == 5) { + sheet.setColumnWidth((short) i, (short) 10000); + } else { + sheet.setColumnWidth((short) i, (short) 8000); + } + } + HSSFCell cell = row.getCell((short) 0); + setCellValue(cell, "Data validation cells"); + cell = row.getCell((short) 1); + setCellValue(cell, "Condition"); + cell = row.getCell((short) 2); + setCellValue(cell, "Allow blank"); + cell = row.getCell((short) 3); + setCellValue(cell, "Prompt box"); + cell = row.getCell((short) 4); + setCellValue(cell, "Error box"); + cell = row.getCell((short) 5); + setCellValue(cell, "Other settings"); + } + + public ValidationAdder createValidationAdder(HSSFCellStyle cellStyle, int dataValidationType) { + return new ValidationAdder(_currentSheet, _style_1, _style_2, cellStyle, dataValidationType); + } + + public void createDVDescriptionRow(String strTypeDescription) { + HSSFSheet sheet = _currentSheet; + HSSFRow row = sheet.getRow(sheet.getPhysicalNumberOfRows()-1); + sheet.addMergedRegion(new CellRangeAddress(sheet.getPhysicalNumberOfRows()-1, sheet.getPhysicalNumberOfRows()-1, 0, 5)); + HSSFCell cell = row.createCell((short)0); + setCellValue(cell, strTypeDescription); + cell.setCellStyle(_style_3); + row = sheet.createRow(sheet.getPhysicalNumberOfRows()); + } + } + + + private void addCustomValidations(WorkbookFormatter wf) { + wf.createSheet("Custom"); + wf.createHeaderRow(); + + ValidationAdder va = wf.createValidationAdder(null, VT.FORMULA); + va.addValidation(OP.BETWEEN, "ISNUMBER($A2)", null, ES.STOP, "ISNUMBER(A2)", "Error box type = STOP", true, true, true); + va.addValidation(OP.BETWEEN, "IF(SUM(A2:A3)=5,TRUE,FALSE)", null, ES.WARNING, "IF(SUM(A2:A3)=5,TRUE,FALSE)", "Error box type = WARNING", false, false, true); + } + + private static void addSimpleNumericValidations(WorkbookFormatter wf) { + // data validation's number types + wf.createSheet("Numbers"); + + // "Whole number" validation type + wf.createDVTypeRow("Whole number"); + wf.createHeaderRow(); + + ValidationAdder va = wf.createValidationAdder(null, VT.INTEGER); + va.addValidation(OP.BETWEEN, "2", "6", ES.STOP, "Between 2 and 6 ", "Error box type = STOP", true, true, true); + va.addValidation(OP.NOT_BETWEEN, "2", "6", ES.INFO, "Not between 2 and 6 ", "Error box type = INFO", false, true, true); + va.addValidation(OP.EQUAL, "=3+2", null, ES.WARNING, "Equal to (3+2)", "Error box type = WARNING", false, false, true); + va.addValidation(OP.NOT_EQUAL, "3", null, ES.WARNING, "Not equal to 3", "-", false, false, false); + va.addValidation(OP.GREATER_THAN, "3", null, ES.WARNING, "Greater than 3", "-", true, false, false); + va.addValidation(OP.LESS_THAN, "3", null, ES.WARNING, "Less than 3", "-", true, true, false); + va.addValidation(OP.GREATER_OR_EQUAL, "4", null, ES.STOP, "Greater than or equal to 4", "Error box type = STOP", true, false, true); + va.addValidation(OP.LESS_OR_EQUAL, "4", null, ES.STOP, "Less than or equal to 4", "-", false, true, false); + + // "Decimal" validation type + wf.createDVTypeRow("Decimal"); + wf.createHeaderRow(); + + va = wf.createValidationAdder(null, VT.DECIMAL); + va.addValidation(OP.BETWEEN, "2", "6", ES.STOP, "Between 2 and 6 ", "Error box type = STOP", true, true, true); + va.addValidation(OP.NOT_BETWEEN, "2", "6", ES.INFO, "Not between 2 and 6 ", "Error box type = INFO", false, true, true); + va.addValidation(OP.EQUAL, "3", null, ES.WARNING, "Equal to 3", "Error box type = WARNING", false, false, true); + va.addValidation(OP.NOT_EQUAL, "3", null, ES.WARNING, "Not equal to 3", "-", false, false, false); + va.addValidation(OP.GREATER_THAN, "=12/6", null, ES.WARNING, "Greater than (12/6)", "-", true, false, false); + va.addValidation(OP.LESS_THAN, "3", null, ES.WARNING, "Less than 3", "-", true, true, false); + va.addValidation(OP.GREATER_OR_EQUAL, "4", null, ES.STOP, "Greater than or equal to 4", "Error box type = STOP", true, false, true); + va.addValidation(OP.LESS_OR_EQUAL, "4", null, ES.STOP, "Less than or equal to 4", "-", false, true, false); + } + + private static void addListValidations(WorkbookFormatter wf, HSSFWorkbook wb) { + final String cellStrValue + = "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 " + + "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 " + + "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 " + + "a b c d e f g h i j k l m n o p r s t u v x y z w 0 1 2 3 4 "; + final String dataSheetName = "list_data"; + // "List" Data Validation type + HSSFSheet fSheet = wf.createSheet("Lists"); + HSSFSheet dataSheet = wb.createSheet(dataSheetName); + + + wf.createDVTypeRow("Explicit lists - list items are explicitly provided"); + wf.createDVDescriptionRow("Disadvantage - sum of item's length should be less than 255 characters"); + wf.createHeaderRow(); + + ValidationAdder va = wf.createValidationAdder(null, VT.LIST); + String listValsDescr = "POIFS,HSSF,HWPF,HPSF"; + String[] listVals = listValsDescr.split(","); + va.addListValidation(listVals, null, listValsDescr, false, false); + va.addListValidation(listVals, null, listValsDescr, false, true); + va.addListValidation(listVals, null, listValsDescr, true, false); + va.addListValidation(listVals, null, listValsDescr, true, true); + + + + wf.createDVTypeRow("Reference lists - list items are taken from others cells"); + wf.createDVDescriptionRow("Advantage - no restriction regarding the sum of item's length"); + wf.createHeaderRow(); + va = wf.createValidationAdder(null, VT.LIST); + String strFormula = "$A$30:$A$39"; + va.addListValidation(null, strFormula, strFormula, false, false); + + strFormula = dataSheetName + "!$A$1:$A$10"; + va.addListValidation(null, strFormula, strFormula, false, false); + HSSFName namedRange = wb.createName(); + namedRange.setNameName("myName"); + namedRange.setReference(dataSheetName + "!$A$2:$A$7"); + strFormula = "myName"; + va.addListValidation(null, strFormula, strFormula, false, false); + strFormula = "offset(myName, 2, 1, 4, 2)"; // Note about last param '2': + // - Excel expects single row or single column when entered in UI, but process this OK otherwise + va.addListValidation(null, strFormula, strFormula, false, false); + + // add list data on same sheet + for (int i = 0; i < 10; i++) { + HSSFRow currRow = fSheet.createRow(i + 29); + setCellValue(currRow.createCell((short) 0), cellStrValue); + } + // add list data on another sheet + for (int i = 0; i < 10; i++) { + HSSFRow currRow = dataSheet.createRow(i + 0); + setCellValue(currRow.createCell((short) 0), "Data a" + i); + setCellValue(currRow.createCell((short) 1), "Data b" + i); + setCellValue(currRow.createCell((short) 2), "Data c" + i); + } + } + + private static void addDateTimeValidations(WorkbookFormatter wf, HSSFWorkbook wb) { + wf.createSheet("Dates and Times"); + + HSSFDataFormat dataFormat = wb.createDataFormat(); + short fmtDate = dataFormat.getFormat("m/d/yyyy"); + short fmtTime = dataFormat.getFormat("h:mm"); + HSSFCellStyle cellStyle_date = wb.createCellStyle(); + cellStyle_date.setDataFormat(fmtDate); + HSSFCellStyle cellStyle_time = wb.createCellStyle(); + cellStyle_time.setDataFormat(fmtTime); + + wf.createDVTypeRow("Date ( cells are already formated as date - m/d/yyyy)"); + wf.createHeaderRow(); + + ValidationAdder va = wf.createValidationAdder(cellStyle_date, VT.DATE); + va.addValidation(OP.BETWEEN, "2004/01/02", "2004/01/06", ES.STOP, "Between 1/2/2004 and 1/6/2004 ", "Error box type = STOP", true, true, true); + va.addValidation(OP.NOT_BETWEEN, "2004/01/01", "2004/01/06", ES.INFO, "Not between 1/2/2004 and 1/6/2004 ", "Error box type = INFO", false, true, true); + va.addValidation(OP.EQUAL, "2004/03/02", null, ES.WARNING, "Equal to 3/2/2004", "Error box type = WARNING", false, false, true); + va.addValidation(OP.NOT_EQUAL, "2004/03/02", null, ES.WARNING, "Not equal to 3/2/2004", "-", false, false, false); + va.addValidation(OP.GREATER_THAN,"=DATEVALUE(\"4-Jul-2001\")", null, ES.WARNING, "Greater than DATEVALUE('4-Jul-2001')", "-", true, false, false); + va.addValidation(OP.LESS_THAN, "2004/03/02", null, ES.WARNING, "Less than 3/2/2004", "-", true, true, false); + va.addValidation(OP.GREATER_OR_EQUAL, "2004/03/02", null, ES.STOP, "Greater than or equal to 3/2/2004", "Error box type = STOP", true, false, true); + va.addValidation(OP.LESS_OR_EQUAL, "2004/03/04", null, ES.STOP, "Less than or equal to 3/4/2004", "-", false, true, false); + + // "Time" validation type + wf.createDVTypeRow("Time ( cells are already formated as time - h:mm)"); + wf.createHeaderRow(); + + va = wf.createValidationAdder(cellStyle_time, VT.TIME); + va.addValidation(OP.BETWEEN, "12:00", "16:00", ES.STOP, "Between 12:00 and 16:00 ", "Error box type = STOP", true, true, true); + va.addValidation(OP.NOT_BETWEEN, "12:00", "16:00", ES.INFO, "Not between 12:00 and 16:00 ", "Error box type = INFO", false, true, true); + va.addValidation(OP.EQUAL, "13:35", null, ES.WARNING, "Equal to 13:35", "Error box type = WARNING", false, false, true); + va.addValidation(OP.NOT_EQUAL, "13:35", null, ES.WARNING, "Not equal to 13:35", "-", false, false, false); + va.addValidation(OP.GREATER_THAN,"12:00", null, ES.WARNING, "Greater than 12:00", "-", true, false, false); + va.addValidation(OP.LESS_THAN, "=1/2", null, ES.WARNING, "Less than (1/2) -> 12:00", "-", true, true, false); + va.addValidation(OP.GREATER_OR_EQUAL, "14:00", null, ES.STOP, "Greater than or equal to 14:00", "Error box type = STOP", true, false, true); + va.addValidation(OP.LESS_OR_EQUAL,"14:00", null, ES.STOP, "Less than or equal to 14:00", "-", false, true, false); + } + + private static void addTextLengthValidations(WorkbookFormatter wf) { + wf.createSheet("Text lengths"); + wf.createHeaderRow(); + + ValidationAdder va = wf.createValidationAdder(null, VT.TEXT_LENGTH); + va.addValidation(OP.BETWEEN, "2", "6", ES.STOP, "Between 2 and 6 ", "Error box type = STOP", true, true, true); + va.addValidation(OP.NOT_BETWEEN, "2", "6", ES.INFO, "Not between 2 and 6 ", "Error box type = INFO", false, true, true); + va.addValidation(OP.EQUAL, "3", null, ES.WARNING, "Equal to 3", "Error box type = WARNING", false, false, true); + va.addValidation(OP.NOT_EQUAL, "3", null, ES.WARNING, "Not equal to 3", "-", false, false, false); + va.addValidation(OP.GREATER_THAN, "3", null, ES.WARNING, "Greater than 3", "-", true, false, false); + va.addValidation(OP.LESS_THAN, "3", null, ES.WARNING, "Less than 3", "-", true, true, false); + va.addValidation(OP.GREATER_OR_EQUAL, "4", null, ES.STOP, "Greater than or equal to 4", "Error box type = STOP", true, false, true); + va.addValidation(OP.LESS_OR_EQUAL, "4", null, ES.STOP, "Less than or equal to 4", "-", false, true, false); + } + + public void testDataValidation() { + log("\nTest no. 2 - Test Excel's Data validation mechanism"); + HSSFWorkbook wb = new HSSFWorkbook(); + WorkbookFormatter wf = new WorkbookFormatter(wb); + + log(" Create sheet for Data Validation's number types ... "); + addSimpleNumericValidations(wf); + log("done !"); + + log(" Create sheet for 'List' Data Validation type ... "); + addListValidations(wf, wb); + log("done !"); + + log(" Create sheet for 'Date' and 'Time' Data Validation types ... "); + addDateTimeValidations(wf, wb); + log("done !"); + + log(" Create sheet for 'Text length' Data Validation type... "); + addTextLengthValidations(wf); + log("done !"); + + // Custom Validation type + log(" Create sheet for 'Custom' Data Validation type ... "); + addCustomValidations(wf); + log("done !"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(22000); + try { + wb.write(baos); + baos.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + byte[] generatedContent = baos.toByteArray(); + boolean isSame; + if (false) { + // TODO - add proof spreadsheet and compare + InputStream proofStream = HSSFTestDataSamples.openSampleFileStream("TestDataValidation.xls"); + isSame = compareStreams(proofStream, generatedContent); + } + isSame = true; + + if (isSame) { + return; + } + File tempDir = new File(System.getProperty("java.io.tmpdir")); + File generatedFile = new File(tempDir, "GeneratedTestDataValidation.xls"); + try { + FileOutputStream fileOut = new FileOutputStream(generatedFile); + fileOut.write(generatedContent); + fileOut.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + PrintStream ps = System.out; + + ps.println("This test case has failed because the generated file differs from proof copy '" + ); // TODO+ proofFile.getAbsolutePath() + "'."); + ps.println("The cause is usually a change to this test, or some common spreadsheet generation code. " + + "The developer has to decide whether the changes were wanted or unwanted."); + ps.println("If the changes to the generated version were unwanted, " + + "make the fix elsewhere (do not modify this test or the proof spreadsheet to get the test working)."); + ps.println("If the changes were wanted, make sure to open the newly generated file in Excel " + + "and verify it manually. The new proof file should be submitted after it is verified to be correct."); + ps.println(""); + ps.println("One other possible (but less likely) cause of a failed test is a problem in the " + + "comparison logic used here. Perhaps some extra file regions need to be ignored."); + ps.println("The generated file has been saved to '" + generatedFile.getAbsolutePath() + "' for manual inspection."); + + fail("Generated file differs from proof copy. See sysout comments for details on how to fix."); + + } + + private static boolean compareStreams(InputStream isA, byte[] generatedContent) { + + InputStream isB = new ByteArrayInputStream(generatedContent); + + // The allowable regions where the generated file can differ from the + // proof should be small (i.e. much less than 1K) + int[] allowableDifferenceRegions = { + 0x0228, 16, // a region of the file containing the OS username + 0x506C, 8, // See RootProperty (super fields _seconds_2 and _days_2) + }; + int[] diffs = StreamUtility.diffStreams(isA, isB, allowableDifferenceRegions); + if (diffs == null) { + return true; + } + System.err.println("Diff from proof: "); + for (int i = 0; i < diffs.length; i++) { + System.err.println("diff at offset: 0x" + Integer.toHexString(diffs[i])); + } + return false; + } + + + + + + /* package */ static void setCellValue(HSSFCell cell, String text) { + cell.setCellValue(new HSSFRichTextString(text)); + } + + public void testAddToExistingSheet() { + + // dvEmpty.xls is a simple one sheet workbook. With a DataValidations header record but no + // DataValidations. It's important that the example has one SHEETPROTECTION record. + // Such a workbook can be created in Excel (2007) by adding datavalidation for one cell + // and then deleting the row that contains the cell. + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("dvEmpty.xls"); + int dvRow = 0; + HSSFSheet sheet = wb.getSheetAt(0); + DVConstraint dc = DVConstraint.createNumericConstraint(VT.INTEGER, OP.EQUAL, "42", null); + HSSFDataValidation dv = new HSSFDataValidation(new CellRangeAddressList(dvRow, dvRow, 0, 0), dc); + + dv.setEmptyCellAllowed(false); + dv.setErrorStyle(ES.STOP); + dv.setShowPromptBox(true); + dv.createErrorBox("Xxx", "Yyy"); + dv.setSuppressDropDownArrow(true); + + sheet.addValidationData(dv); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + wb.write(baos); + } catch (IOException e) { + throw new RuntimeException(e); + } + + byte[] wbData = baos.toByteArray(); + + if (false) { // TODO (Jul 2008) fix EventRecordFactory to process unknown records, (and DV records for that matter) + EventRecordFactory erf = new EventRecordFactory(); + ERFListener erfListener = null; // new MyERFListener(); + erf.registerListener(erfListener, null); + try { + POIFSFileSystem fs = new POIFSFileSystem(new ByteArrayInputStream(baos.toByteArray())); + erf.processRecords(fs.createDocumentInputStream("Workbook")); + } catch (RecordFormatException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + // else verify record ordering by navigating the raw bytes + + byte[] dvHeaderRecStart= { (byte)0xB2, 0x01, 0x12, 0x00, }; + int dvHeaderOffset = findIndex(wbData, dvHeaderRecStart); + assertTrue(dvHeaderOffset > 0); + int nextRecIndex = dvHeaderOffset + 22; + int nextSid + = ((wbData[nextRecIndex + 0] << 0) & 0x00FF) + + ((wbData[nextRecIndex + 1] << 8) & 0xFF00) + ; + // nextSid should be for a DVRecord. If anything comes between the DV header record + // and the DV records, Excel will not be able to open the workbook without error. + + if (nextSid == 0x0867) { + throw new AssertionFailedError("Identified bug 45519"); + } + assertEquals(DVRecord.sid, nextSid); + } + private int findIndex(byte[] largeData, byte[] searchPattern) { + byte firstByte = searchPattern[0]; + for (int i = 0; i < largeData.length; i++) { + if(largeData[i] != firstByte) { + continue; + } + boolean match = true; + for (int j = 1; j < searchPattern.length; j++) { + if(searchPattern[j] != largeData[i+j]) { + match = false; + break; + } + } + if (match) { + return i; + } + } + return -1; + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java index 369aa2665..978f43577 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java @@ -176,4 +176,37 @@ public final class TestHSSFComment extends TestCase { } } + + public void testDeleteComments() throws Exception { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("SimpleWithComments.xls"); + HSSFSheet sheet = wb.getSheetAt(0); + + // Zap from rows 1 and 3 + assertNotNull(sheet.getRow(0).getCell(1).getCellComment()); + assertNotNull(sheet.getRow(1).getCell(1).getCellComment()); + assertNotNull(sheet.getRow(2).getCell(1).getCellComment()); + + sheet.getRow(0).getCell(1).removeCellComment(); + sheet.getRow(2).getCell(1).setCellComment(null); + + // Check gone so far + assertNull(sheet.getRow(0).getCell(1).getCellComment()); + assertNotNull(sheet.getRow(1).getCell(1).getCellComment()); + assertNull(sheet.getRow(2).getCell(1).getCellComment()); + + // Save and re-load + ByteArrayOutputStream out = new ByteArrayOutputStream(); + wb.write(out); + out.close(); + wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray())); + + // Check + assertNull(sheet.getRow(0).getCell(1).getCellComment()); + assertNotNull(sheet.getRow(1).getCell(1).getCellComment()); + assertNull(sheet.getRow(2).getCell(1).getCellComment()); + +// FileOutputStream fout = new FileOutputStream("/tmp/c.xls"); +// wb.write(fout); +// fout.close(); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFConditionalFormatting.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFConditionalFormatting.java index 6dbcf815d..fcb0b8f41 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFConditionalFormatting.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFConditionalFormatting.java @@ -21,7 +21,9 @@ import junit.framework.TestCase; import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator; import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.Region; + /** * * @author Dmitriy Kumshayev @@ -57,9 +59,8 @@ public final class TestHSSFConditionalFormatting extends TestCase }; short col = 1; - Region [] regions = - { - new Region(0,col,65535,col) + CellRangeAddress [] regions = { + new CellRangeAddress(0, 65535, col, col) }; sheetCF.addConditionalFormatting(regions, cfRules); @@ -72,14 +73,14 @@ public final class TestHSSFConditionalFormatting extends TestCase HSSFConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0); assertNotNull(cf); - regions = cf.getFormattingRegions(); + regions = cf.getFormattingRanges(); assertNotNull(regions); assertEquals(1, regions.length); - Region r = regions[0]; - assertEquals(1, r.getColumnFrom()); - assertEquals(1, r.getColumnTo()); - assertEquals(0, r.getRowFrom()); - assertEquals(65535, r.getRowTo()); + CellRangeAddress r = regions[0]; + assertEquals(1, r.getFirstColumn()); + assertEquals(1, r.getLastColumn()); + assertEquals(0, r.getFirstRow()); + assertEquals(65535, r.getLastRow()); assertEquals(2, cf.getNumberOfRules()); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java index f865d6e49..e24f30da6 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java @@ -19,6 +19,7 @@ package org.apache.poi.hssf.usermodel; import java.text.DecimalFormat; import java.text.Format; +import java.util.Date; import java.util.Iterator; import junit.framework.TestCase; diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java index 281d1b1cb..377a8ffe2 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java @@ -39,7 +39,7 @@ import org.apache.poi.hssf.model.Workbook; * @author Alex Jacoby (ajacoby at gmail.com) * @version %I%, %G% */ -public class TestHSSFDateUtil extends TestCase { +public final class TestHSSFDateUtil extends TestCase { public static final int CALENDAR_JANUARY = 0; public static final int CALENDAR_FEBRUARY = 1; @@ -47,11 +47,6 @@ public class TestHSSFDateUtil extends TestCase { public static final int CALENDAR_APRIL = 3; public static final int CALENDAR_JULY = 6; public static final int CALENDAR_OCTOBER = 9; - - public TestHSSFDateUtil(String s) - { - super(s); - } /** * Checks the date conversion functions in the HSSFDateUtil class. @@ -193,14 +188,13 @@ public class TestHSSFDateUtil extends TestCase { } /** - * Tests that we deal with timezones properly + * Tests that we deal with time-zones properly */ public void testCalendarConversion() { GregorianCalendar date = new GregorianCalendar(2002, 0, 1, 12, 1, 1); Date expected = date.getTime(); - double expectedExcel = HSSFDateUtil.getExcelDate(expected); - // Iteratating over the hours exposes any rounding issues. + // Iterating over the hours exposes any rounding issues. for (int hour = -12; hour <= 12; hour++) { String id = "GMT" + (hour < 0 ? "" : "+") + hour + ":00"; @@ -209,7 +203,7 @@ public class TestHSSFDateUtil extends TestCase { double excelDate = HSSFDateUtil.getExcelDate(date, false); Date javaDate = HSSFDateUtil.getJavaDate(excelDate); - // Should match despite timezone + // Should match despite time-zone assertEquals("Checking timezone " + id, expected.getTime(), javaDate.getTime()); } } @@ -402,7 +396,11 @@ public class TestHSSFDateUtil extends TestCase { assertEquals(34519.0, HSSFDateUtil.getExcelDate(createDate(1998, CALENDAR_JULY, 5), true), 0.00001); } - private Date createDate(int year, int month, int day) { + /** + * @param month zero based + * @param day one based + */ + private static Date createDate(int year, int month, int day) { Calendar c = new GregorianCalendar(); c.set(year, month, day, 0, 0, 0); c.set(Calendar.MILLISECOND, 0); @@ -420,10 +418,18 @@ public class TestHSSFDateUtil extends TestCase { calendar = new GregorianCalendar(1901, 0, 1); assertEquals("Checking absolute day (1 Jan 1901)", 366, HSSFDateUtil.absoluteDay(calendar, false)); } + + public void testConvertTime() { + + final double delta = 1E-7; // a couple of digits more accuracy than strictly required + assertEquals(0.5, HSSFDateUtil.convertTime("12:00"), delta); + assertEquals(2.0/3, HSSFDateUtil.convertTime("16:00"), delta); + assertEquals(0.0000116, HSSFDateUtil.convertTime("0:00:01"), delta); + assertEquals(0.7330440, HSSFDateUtil.convertTime("17:35:35"), delta); + } - public static void main(String [] args) { - System.out - .println("Testing org.apache.poi.hssf.usermodel.TestHSSFDateUtil"); - junit.textui.TestRunner.run(TestHSSFDateUtil.class); + public void testParseDate() { + assertEquals(createDate(2008, Calendar.AUGUST, 3), HSSFDateUtil.parseYYYYMMDDDate("2008/08/03")); + assertEquals(createDate(1994, Calendar.MAY, 1), HSSFDateUtil.parseYYYYMMDDDate("1994/05/01")); } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 90971b3c0..6fcd38498 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -36,6 +36,7 @@ import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.WSBoolRecord; import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.ss.util.Region; +import org.apache.poi.ss.util.CellRangeAddress; /** * Tests HSSFSheet. This test case is very incomplete at the moment. @@ -476,15 +477,15 @@ public final class TestHSSFSheet extends TestCase { public void testRemoveMerged() { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet(); - Region region = new Region(0, (short)0, 1, (short)1); + CellRangeAddress region = new CellRangeAddress(0, 1, 0, 1); sheet.addMergedRegion(region); - region = new Region(1, (short)0, 2, (short)1); + region = new CellRangeAddress(1, 2, 0, 1); sheet.addMergedRegion(region); sheet.removeMergedRegion(0); - region = sheet.getMergedRegionAt(0); - assertEquals("Left over region should be starting at row 1", 1, region.getRowFrom()); + region = sheet.getMergedRegion(0); + assertEquals("Left over region should be starting at row 1", 1, region.getFirstRow()); sheet.removeMergedRegion(0); @@ -496,15 +497,15 @@ public final class TestHSSFSheet extends TestCase { sheet.removeMergedRegion(0); assertEquals("there should now be zero merged regions!", 0, sheet.getNumMergedRegions()); //add it again! - region.setRowTo(4); + region.setLastRow(4); sheet.addMergedRegion(region); assertEquals("there should now be one merged region!", 1, sheet.getNumMergedRegions()); //should exist now! assertTrue("there isn't more than one merged region in there", 1 <= sheet.getNumMergedRegions()); - region = sheet.getMergedRegionAt(0); - assertEquals("the merged row to doesnt match the one we put in ", 4, region.getRowTo()); + region = sheet.getMergedRegion(0); + assertEquals("the merged row to doesnt match the one we put in ", 4, region.getLastRow()); } public void testShiftMerged() { @@ -518,13 +519,13 @@ public final class TestHSSFSheet extends TestCase { cell = row.createCell((short)1); cell.setCellValue(new HSSFRichTextString("second row, second cell")); - Region region = new Region(1, (short)0, 1, (short)1); + CellRangeAddress region = new CellRangeAddress(1, 1, 0, 1); sheet.addMergedRegion(region); sheet.shiftRows(1, 1, 1); - region = sheet.getMergedRegionAt(0); - assertEquals("Merged region not moved over to row 2", 2, region.getRowFrom()); + region = sheet.getMergedRegion(0); + assertEquals("Merged region not moved over to row 2", 2, region.getFirstRow()); } /** @@ -683,7 +684,7 @@ public final class TestHSSFSheet extends TestCase { assertTrue("Column autosized with only one row: wrong width", sheet.getColumnWidth((short)0) <= maxWithRow1And2); //create a region over the 2nd row and auto size the first column - sheet.addMergedRegion(new Region(1,(short)0,1,(short)1)); + sheet.addMergedRegion(new CellRangeAddress(1,1,0,1)); sheet.autoSizeColumn((short)0); HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestWorkbook.java b/src/testcases/org/apache/poi/hssf/usermodel/TestWorkbook.java index a5402f64c..916f6f4f0 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestWorkbook.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestWorkbook.java @@ -32,6 +32,7 @@ import org.apache.poi.hssf.record.LabelSSTRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.Region; import org.apache.poi.util.TempFile; @@ -42,7 +43,7 @@ import org.apache.poi.util.TempFile; * @author Greg Merrill * @author Siggi Cherem */ -public class TestWorkbook extends TestCase { +public final class TestWorkbook extends TestCase { private static final String LAST_NAME_KEY = "lastName"; private static final String FIRST_NAME_KEY = "firstName"; private static final String SSN_KEY = "ssn"; @@ -260,10 +261,10 @@ public class TestWorkbook extends TestCase { HSSFWorkbook workbook = openSample("Employee.xls"); HSSFSheet sheet = workbook.getSheetAt(0); - assertEquals(EMPLOYEE_INFORMATION, sheet.getRow(1).getCell(1).getStringCellValue()); - assertEquals(LAST_NAME_KEY, sheet.getRow(3).getCell(2).getStringCellValue()); - assertEquals(FIRST_NAME_KEY, sheet.getRow(4).getCell(2).getStringCellValue()); - assertEquals(SSN_KEY, sheet.getRow(5).getCell(2).getStringCellValue()); + assertEquals(EMPLOYEE_INFORMATION, sheet.getRow(1).getCell(1).getRichStringCellValue().getString()); + assertEquals(LAST_NAME_KEY, sheet.getRow(3).getCell(2).getRichStringCellValue().getString()); + assertEquals(FIRST_NAME_KEY, sheet.getRow(4).getCell(2).getRichStringCellValue().getString()); + assertEquals(SSN_KEY, sheet.getRow(5).getCell(2).getRichStringCellValue().getString()); } /** @@ -318,13 +319,13 @@ public class TestWorkbook extends TestCase { sheet = workbook.getSheetAt(0); cell = sheet.getRow(0).getCell(1); - assertEquals(REPLACED, cell.getStringCellValue()); + assertEquals(REPLACED, cell.getRichStringCellValue().getString()); cell = sheet.getRow(0).getCell(0); - assertEquals(DO_NOT_REPLACE, cell.getStringCellValue()); + assertEquals(DO_NOT_REPLACE, cell.getRichStringCellValue().getString()); cell = sheet.getRow(1).getCell(0); - assertEquals(REPLACED, cell.getStringCellValue()); + assertEquals(REPLACED, cell.getRichStringCellValue().getString()); cell = sheet.getRow(1).getCell(1); - assertEquals(DO_NOT_REPLACE, cell.getStringCellValue()); + assertEquals(DO_NOT_REPLACE, cell.getRichStringCellValue().getString()); } /** @@ -388,10 +389,10 @@ public class TestWorkbook extends TestCase { workbook = HSSFTestDataSamples.writeOutAndReadBack(workbook); sheet = workbook.getSheetAt(0); - assertEquals(EMPLOYEE_INFORMATION, sheet.getRow(1).getCell(1).getStringCellValue()); - assertEquals(LAST_NAME_VALUE, sheet.getRow(3).getCell(2).getStringCellValue()); - assertEquals(FIRST_NAME_VALUE, sheet.getRow(4).getCell(2).getStringCellValue()); - assertEquals(SSN_VALUE, sheet.getRow(5).getCell(2).getStringCellValue()); + assertEquals(EMPLOYEE_INFORMATION, sheet.getRow(1).getCell(1).getRichStringCellValue().getString()); + assertEquals(LAST_NAME_VALUE, sheet.getRow(3).getCell(2).getRichStringCellValue().getString()); + assertEquals(FIRST_NAME_VALUE, sheet.getRow(4).getCell(2).getRichStringCellValue().getString()); + assertEquals(SSN_VALUE, sheet.getRow(5).getCell(2).getRichStringCellValue().getString()); } /** @@ -421,26 +422,17 @@ public class TestWorkbook extends TestCase { * HSSFSheet last row or first row is incorrect.

    * */ - - public void testWriteModifySheetMerged() - throws IOException - { - File file = TempFile.createTempFile("testWriteSheetMerged", - ".xls"); - FileOutputStream out = new FileOutputStream(file); - FileInputStream in = null; + public void testWriteModifySheetMerged() { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet s = wb.createSheet(); - HSSFRow r = null; - HSSFCell c = null; for (short rownum = ( short ) 0; rownum < 100; rownum++) { - r = s.createRow(rownum); + HSSFRow r = s.createRow(rownum); for (short cellnum = ( short ) 0; cellnum < 50; cellnum += 2) { - c = r.createCell(cellnum); + HSSFCell c = r.createCell(cellnum); c.setCellValue(rownum * 10000 + cellnum + ((( double ) rownum / 1000) + (( double ) cellnum / 10000))); @@ -448,33 +440,27 @@ public class TestWorkbook extends TestCase { c.setCellValue(new HSSFRichTextString("TEST")); } } - s.addMergedRegion(new Region(( short ) 0, ( short ) 0, ( short ) 10, - ( short ) 10)); - s.addMergedRegion(new Region(( short ) 30, ( short ) 5, ( short ) 40, - ( short ) 15)); - wb.write(out); - out.close(); + s.addMergedRegion(new CellRangeAddress(0, 10, 0, 10)); + s.addMergedRegion(new CellRangeAddress(30, 40, 5, 15)); sanityChecker.checkHSSFWorkbook(wb); - in = new FileInputStream(file); - wb = new HSSFWorkbook(new POIFSFileSystem(in)); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + s = wb.getSheetAt(0); - Region r1 = s.getMergedRegionAt(0); - Region r2 = s.getMergedRegionAt(1); + CellRangeAddress r1 = s.getMergedRegion(0); + CellRangeAddress r2 = s.getMergedRegion(1); - in.close(); - - // System.out.println(file.length()); - // assertEquals("FILE LENGTH == 87552",file.length(), 87552); - // System.out.println(s.getLastRowNum()); - assertEquals("REGION1 = 0,0,10,10", 0, - new Region(( short ) 0, ( short ) 0, ( short ) 10, - ( short ) 10).compareTo(r1)); - assertEquals("REGION2 == 30,5,40,15", 0, - new Region(( short ) 30, ( short ) 5, ( short ) 40, - ( short ) 15).compareTo(r2)); + confirmRegion(new CellRangeAddress(0, 10, 0, 10), r1); + confirmRegion(new CellRangeAddress(30, 40,5, 15), r2); } - /** + private static void confirmRegion(CellRangeAddress ra, CellRangeAddress rb) { + assertEquals(ra.getFirstRow(), rb.getFirstRow()); + assertEquals(ra.getLastRow(), rb.getLastRow()); + assertEquals(ra.getFirstColumn(), rb.getFirstColumn()); + assertEquals(ra.getLastColumn(), rb.getLastColumn()); + } + + /** * Test the backup field gets set as expected. */