diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index b2dea5c407..380ecb923c 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -44,6 +44,8 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 24207 - added HSSFName.isDeleted() to check if the name points to cell that no longer exists + 40414 - fixed selected/active sheet after removing sheet from workbook 44523 - fixed workbook sheet selection and focus 45000 - Fixed NPE in ListLevel when numberText is null 44985 - Properly update TextSpecInfoAtom when the parent text is changed diff --git a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml index cdad1392be..7523f500d8 100644 --- a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml +++ b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml @@ -1284,6 +1284,20 @@ Examples: Cell c = r.getCell(crefs[j].getCol()); // Do something with this corner cell } + } + +

+ Note, when a cell is deleted, Excel does not delete the + attached named range. As result, workbook can contain + named ranges that point to cells that no longer exist. + You should check the validity of a reference before + constructing AreaReference +

+ + if(hssfName.isDeleted()){ + //named range points to a deleted cell. + } else { + AreaReference ref = new AreaReference(hssfName.getReference()); } diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 75117754bd..ebf869ce95 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -41,6 +41,8 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx
+ 24207 - added HSSFName.isDeleted() to check if the name points to cell that no longer exists + 40414 - fixed selected/active sheet after removing sheet from workbook 44523 - fixed workbook sheet selection and focus 45000 - Fixed NPE in ListLevel when numberText is null 44985 - Properly update TextSpecInfoAtom when the parent text is changed diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 242a85f45f..cd43651912 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -15,22 +15,20 @@ limitations under the License. ==================================================================== */ -/* - * BiffViewer.java - * - * Created on November 13, 2001, 9:23 AM - */ package org.apache.poi.hssf.dev; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + import org.apache.poi.hssf.record.*; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.HexDump; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - /** * Utillity for reading in BIFF8 records and displaying data from them. * @@ -38,38 +36,26 @@ import java.util.ArrayList; *@author Glen Stampoultzis (glens at apache.org) *@see #main */ - -public class BiffViewer { - String filename; +public final class BiffViewer { + private final File _inputFile; private boolean dump; + private final PrintStream _ps; - /** - * Creates new BiffViewer - * - *@param args - */ - - public BiffViewer(String[] args) { - if (args.length > 0) { - filename = args[0]; - } else { - System.out.println("BIFFVIEWER REQUIRES A FILENAME***"); - } + public BiffViewer(File inFile, PrintStream ps) { + _inputFile = inFile; + _ps = ps; } /** * Method run starts up BiffViewer... */ - public void run() { try { - POIFSFileSystem fs = - new POIFSFileSystem(new FileInputStream(filename)); - InputStream stream = - fs.createDocumentInputStream("Workbook"); - createRecords(stream, dump); + POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(_inputFile)); + InputStream stream = fs.createDocumentInputStream("Workbook"); + createRecords(stream, dump, _ps); } catch (Exception e) { e.printStackTrace(); } @@ -86,451 +72,306 @@ public class BiffViewer { * InputStream *@exception RecordFormatException on error processing the InputStream */ - - public static Record[] createRecords(InputStream in, boolean dump) + public static Record[] createRecords(InputStream in, boolean dump, PrintStream ps) throws RecordFormatException { ArrayList records = new ArrayList(); RecordDetails activeRecord = null; - try { - BiffviewRecordInputStream recStream = new BiffviewRecordInputStream(in); - while (recStream.hasNextRecord()) { + BiffviewRecordInputStream recStream = new BiffviewRecordInputStream(in); + while (recStream.hasNextRecord()) { recStream.nextRecord(); if (recStream.getSid() != 0) { - Record record = createRecord (recStream); + Record record = createRecord (recStream); if (record.getSid() != ContinueRecord.sid) { records.add(record); if (activeRecord != null) - activeRecord.dump(); - activeRecord = new RecordDetails(recStream.getSid(), recStream.getLength(), (int)recStream.getPos(), record); + activeRecord.dump(ps); + activeRecord = new RecordDetails(recStream.getSid(), recStream.getLength(), (int)recStream.getPos(), record); } if (dump) { - recStream.dumpBytes(); - } + recStream.dumpBytes(ps); } - } - activeRecord.dump(); - } catch (IOException e) { - throw new RecordFormatException("Error reading bytes", e); + } + } + if (activeRecord != null) { + activeRecord.dump(ps); } Record[] retval = new Record[records.size()]; - - retval = (Record[]) records.toArray(retval); + records.toArray(retval); return retval; } - private static void dumpNormal(Record record, int startloc, short rectype, short recsize) - { - System.out.println("Offset 0x" + Integer.toHexString(startloc) + " (" + startloc + ")"); - System.out.println( "recordid = 0x" + Integer.toHexString( rectype ) + ", size = " + recsize ); - System.out.println( record.toString() ); - - } /** - * Essentially a duplicate of RecordFactory. Kept seperate as not to screw + * Essentially a duplicate of RecordFactory. Kept separate as not to screw * up non-debug operations. * */ private static Record createRecord( RecordInputStream in ) { - Record retval = null; - switch ( in.getSid() ) { - case ChartRecord.sid: - retval = new ChartRecord( in ); - break; + return new ChartRecord( in ); case ChartFormatRecord.sid: - retval = new ChartFormatRecord( in ); - break; + return new ChartFormatRecord( in ); case SeriesRecord.sid: - retval = new SeriesRecord( in ); - break; + return new SeriesRecord( in ); case BeginRecord.sid: - retval = new BeginRecord( in ); - break; + return new BeginRecord( in ); case EndRecord.sid: - retval = new EndRecord( in ); - break; + return new EndRecord( in ); case BOFRecord.sid: - retval = new BOFRecord( in ); - break; + return new BOFRecord( in ); case InterfaceHdrRecord.sid: - retval = new InterfaceHdrRecord( in ); - break; + return new InterfaceHdrRecord( in ); case MMSRecord.sid: - retval = new MMSRecord( in ); - break; + return new MMSRecord( in ); case InterfaceEndRecord.sid: - retval = new InterfaceEndRecord( in ); - break; + return new InterfaceEndRecord( in ); case WriteAccessRecord.sid: - retval = new WriteAccessRecord( in ); - break; + return new WriteAccessRecord( in ); case CodepageRecord.sid: - retval = new CodepageRecord( in ); - break; + return new CodepageRecord( in ); case DSFRecord.sid: - retval = new DSFRecord( in ); - break; + return new DSFRecord( in ); case TabIdRecord.sid: - retval = new TabIdRecord( in ); - break; + return new TabIdRecord( in ); case FnGroupCountRecord.sid: - retval = new FnGroupCountRecord( in ); - break; + return new FnGroupCountRecord( in ); case WindowProtectRecord.sid: - retval = new WindowProtectRecord( in ); - break; + return new WindowProtectRecord( in ); case ProtectRecord.sid: - retval = new ProtectRecord( in ); - break; + return new ProtectRecord( in ); case PasswordRecord.sid: - retval = new PasswordRecord( in ); - break; + return new PasswordRecord( in ); case ProtectionRev4Record.sid: - retval = new ProtectionRev4Record( in ); - break; + return new ProtectionRev4Record( in ); case PasswordRev4Record.sid: - retval = new PasswordRev4Record( in ); - break; + return new PasswordRev4Record( in ); case WindowOneRecord.sid: - retval = new WindowOneRecord( in ); - break; + return new WindowOneRecord( in ); case BackupRecord.sid: - retval = new BackupRecord( in ); - break; + return new BackupRecord( in ); case HideObjRecord.sid: - retval = new HideObjRecord( in ); - break; + return new HideObjRecord( in ); case DateWindow1904Record.sid: - retval = new DateWindow1904Record( in ); - break; + return new DateWindow1904Record( in ); case PrecisionRecord.sid: - retval = new PrecisionRecord( in ); - break; + return new PrecisionRecord( in ); case RefreshAllRecord.sid: - retval = new RefreshAllRecord( in ); - break; + return new RefreshAllRecord( in ); case BookBoolRecord.sid: - retval = new BookBoolRecord( in ); - break; + return new BookBoolRecord( in ); case FontRecord.sid: - retval = new FontRecord( in ); - break; + return new FontRecord( in ); case FormatRecord.sid: - retval = new FormatRecord( in ); - break; + return new FormatRecord( in ); case ExtendedFormatRecord.sid: - retval = new ExtendedFormatRecord( in ); - break; + return new ExtendedFormatRecord( in ); case StyleRecord.sid: - retval = new StyleRecord( in ); - break; + return new StyleRecord( in ); case UseSelFSRecord.sid: - retval = new UseSelFSRecord( in ); - break; + return new UseSelFSRecord( in ); case BoundSheetRecord.sid: - retval = new BoundSheetRecord( in ); - break; + return new BoundSheetRecord( in ); case CountryRecord.sid: - retval = new CountryRecord( in ); - break; + return new CountryRecord( in ); case SSTRecord.sid: - retval = new SSTRecord( in ); - break; + return new SSTRecord( in ); case ExtSSTRecord.sid: - retval = new ExtSSTRecord( in ); - break; + return new ExtSSTRecord( in ); case EOFRecord.sid: - retval = new EOFRecord( in ); - break; + return new EOFRecord( in ); case IndexRecord.sid: - retval = new IndexRecord( in ); - break; + return new IndexRecord( in ); case CalcModeRecord.sid: - retval = new CalcModeRecord( in ); - break; + return new CalcModeRecord( in ); case CalcCountRecord.sid: - retval = new CalcCountRecord( in ); - break; + return new CalcCountRecord( in ); case RefModeRecord.sid: - retval = new RefModeRecord( in ); - break; + return new RefModeRecord( in ); case IterationRecord.sid: - retval = new IterationRecord( in ); - break; + return new IterationRecord( in ); case DeltaRecord.sid: - retval = new DeltaRecord( in ); - break; + return new DeltaRecord( in ); case SaveRecalcRecord.sid: - retval = new SaveRecalcRecord( in ); - break; + return new SaveRecalcRecord( in ); case PrintHeadersRecord.sid: - retval = new PrintHeadersRecord( in ); - break; + return new PrintHeadersRecord( in ); case PrintGridlinesRecord.sid: - retval = new PrintGridlinesRecord( in ); - break; + return new PrintGridlinesRecord( in ); case GridsetRecord.sid: - retval = new GridsetRecord( in ); - break; + return new GridsetRecord( in ); case DrawingGroupRecord.sid: - retval = new DrawingGroupRecord( in ); - break; + return new DrawingGroupRecord( in ); case DrawingRecordForBiffViewer.sid: - retval = new DrawingRecordForBiffViewer( in ); - break; + return new DrawingRecordForBiffViewer( in ); case DrawingSelectionRecord.sid: - retval = new DrawingSelectionRecord( in ); - break; + return new DrawingSelectionRecord( in ); case GutsRecord.sid: - retval = new GutsRecord( in ); - break; + return new GutsRecord( in ); case DefaultRowHeightRecord.sid: - retval = new DefaultRowHeightRecord( in ); - break; + return new DefaultRowHeightRecord( in ); case WSBoolRecord.sid: - retval = new WSBoolRecord( in ); - break; + return new WSBoolRecord( in ); case HeaderRecord.sid: - retval = new HeaderRecord( in ); - break; + return new HeaderRecord( in ); case FooterRecord.sid: - retval = new FooterRecord( in ); - break; + return new FooterRecord( in ); case HCenterRecord.sid: - retval = new HCenterRecord( in ); - break; + return new HCenterRecord( in ); case VCenterRecord.sid: - retval = new VCenterRecord( in ); - break; + return new VCenterRecord( in ); case PrintSetupRecord.sid: - retval = new PrintSetupRecord( in ); - break; + return new PrintSetupRecord( in ); case DefaultColWidthRecord.sid: - retval = new DefaultColWidthRecord( in ); - break; + return new DefaultColWidthRecord( in ); case DimensionsRecord.sid: - retval = new DimensionsRecord( in ); - break; + return new DimensionsRecord( in ); case RowRecord.sid: - retval = new RowRecord( in ); - break; + return new RowRecord( in ); case LabelSSTRecord.sid: - retval = new LabelSSTRecord( in ); - break; + return new LabelSSTRecord( in ); case RKRecord.sid: - retval = new RKRecord( in ); - break; + return new RKRecord( in ); case NumberRecord.sid: - retval = new NumberRecord( in ); - break; + return new NumberRecord( in ); case DBCellRecord.sid: - retval = new DBCellRecord( in ); - break; + return new DBCellRecord( in ); case WindowTwoRecord.sid: - retval = new WindowTwoRecord( in ); - break; + return new WindowTwoRecord( in ); case SelectionRecord.sid: - retval = new SelectionRecord( in ); - break; + return new SelectionRecord( in ); case ContinueRecord.sid: - retval = new ContinueRecord( in ); - break; + return new ContinueRecord( in ); case LabelRecord.sid: - retval = new LabelRecord( in ); - break; + return new LabelRecord( in ); case MulRKRecord.sid: - retval = new MulRKRecord( in ); - break; + return new MulRKRecord( in ); case MulBlankRecord.sid: - retval = new MulBlankRecord( in ); - break; + return new MulBlankRecord( in ); case BlankRecord.sid: - retval = new BlankRecord( in ); - break; + return new BlankRecord( in ); case BoolErrRecord.sid: - retval = new BoolErrRecord( in ); - break; + return new BoolErrRecord( in ); case ColumnInfoRecord.sid: - retval = new ColumnInfoRecord( in ); - break; + return new ColumnInfoRecord( in ); case MergeCellsRecord.sid: - retval = new MergeCellsRecord( in ); - break; + return new MergeCellsRecord( in ); case AreaRecord.sid: - retval = new AreaRecord( in ); - break; + return new AreaRecord( in ); case DataFormatRecord.sid: - retval = new DataFormatRecord( in ); - break; + return new DataFormatRecord( in ); case BarRecord.sid: - retval = new BarRecord( in ); - break; + return new BarRecord( in ); case DatRecord.sid: - retval = new DatRecord( in ); - break; + return new DatRecord( in ); case PlotGrowthRecord.sid: - retval = new PlotGrowthRecord( in ); - break; + return new PlotGrowthRecord( in ); case UnitsRecord.sid: - retval = new UnitsRecord( in ); - break; + return new UnitsRecord( in ); case FrameRecord.sid: - retval = new FrameRecord( in ); - break; + return new FrameRecord( in ); case ValueRangeRecord.sid: - retval = new ValueRangeRecord( in ); - break; + return new ValueRangeRecord( in ); case SeriesListRecord.sid: - retval = new SeriesListRecord( in ); - break; + return new SeriesListRecord( in ); case FontBasisRecord.sid: - retval = new FontBasisRecord( in ); - break; + return new FontBasisRecord( in ); case FontIndexRecord.sid: - retval = new FontIndexRecord( in ); - break; + return new FontIndexRecord( in ); case LineFormatRecord.sid: - retval = new LineFormatRecord( in ); - break; + return new LineFormatRecord( in ); case AreaFormatRecord.sid: - retval = new AreaFormatRecord( in ); - break; + return new AreaFormatRecord( in ); case LinkedDataRecord.sid: - retval = new LinkedDataRecord( in ); - break; + return new LinkedDataRecord( in ); case FormulaRecord.sid: - retval = new FormulaRecord( in ); - break; + return new FormulaRecord( in ); case SheetPropertiesRecord.sid: - retval = new SheetPropertiesRecord( in ); - break; + return new SheetPropertiesRecord( in ); case DefaultDataLabelTextPropertiesRecord.sid: - retval = new DefaultDataLabelTextPropertiesRecord( in ); - break; + return new DefaultDataLabelTextPropertiesRecord( in ); case TextRecord.sid: - retval = new TextRecord( in ); - break; + return new TextRecord( in ); case AxisParentRecord.sid: - retval = new AxisParentRecord( in ); - break; + return new AxisParentRecord( in ); case AxisLineFormatRecord.sid: - retval = new AxisLineFormatRecord( in ); - break; + return new AxisLineFormatRecord( in ); case SupBookRecord.sid: - retval = new SupBookRecord( in ); - break; + return new SupBookRecord( in ); case ExternSheetRecord.sid: - retval = new ExternSheetRecord( in ); - break; + return new ExternSheetRecord( in ); case SCLRecord.sid: - retval = new SCLRecord( in ); - break; + return new SCLRecord( in ); case SeriesToChartGroupRecord.sid: - retval = new SeriesToChartGroupRecord( in ); - break; + return new SeriesToChartGroupRecord( in ); case AxisUsedRecord.sid: - retval = new AxisUsedRecord( in ); - break; + return new AxisUsedRecord( in ); case AxisRecord.sid: - retval = new AxisRecord( in ); - break; + return new AxisRecord( in ); case CategorySeriesAxisRecord.sid: - retval = new CategorySeriesAxisRecord( in ); - break; + return new CategorySeriesAxisRecord( in ); case AxisOptionsRecord.sid: - retval = new AxisOptionsRecord( in ); - break; + return new AxisOptionsRecord( in ); case TickRecord.sid: - retval = new TickRecord( in ); - break; + return new TickRecord( in ); case SeriesTextRecord.sid: - retval = new SeriesTextRecord( in ); - break; + return new SeriesTextRecord( in ); case ObjectLinkRecord.sid: - retval = new ObjectLinkRecord( in ); - break; + return new ObjectLinkRecord( in ); case PlotAreaRecord.sid: - retval = new PlotAreaRecord( in ); - break; + return new PlotAreaRecord( in ); case SeriesIndexRecord.sid: - retval = new SeriesIndexRecord( in ); - break; + return new SeriesIndexRecord( in ); case LegendRecord.sid: - retval = new LegendRecord( in ); - break; + return new LegendRecord( in ); case LeftMarginRecord.sid: - retval = new LeftMarginRecord( in ); - break; + return new LeftMarginRecord( in ); case RightMarginRecord.sid: - retval = new RightMarginRecord( in ); - break; + return new RightMarginRecord( in ); case TopMarginRecord.sid: - retval = new TopMarginRecord( in ); - break; + return new TopMarginRecord( in ); case BottomMarginRecord.sid: - retval = new BottomMarginRecord( in ); - break; + return new BottomMarginRecord( in ); case PaletteRecord.sid: - retval = new PaletteRecord( in ); - break; + return new PaletteRecord( in ); case StringRecord.sid: - retval = new StringRecord( in ); - break; + return new StringRecord( in ); case NameRecord.sid: - retval = new NameRecord( in ); - break; + return new NameRecord( in ); case PaneRecord.sid: - retval = new PaneRecord( in ); - break; + return new PaneRecord( in ); case SharedFormulaRecord.sid: - retval = new SharedFormulaRecord( in); - break; + return new SharedFormulaRecord( in); case ObjRecord.sid: - retval = new ObjRecord( in); - break; + return new ObjRecord( in); case TextObjectRecord.sid: - retval = new TextObjectRecord( in); - break; + return new TextObjectRecord( in); case HorizontalPageBreakRecord.sid: - retval = new HorizontalPageBreakRecord( in); - break; + return new HorizontalPageBreakRecord( in); case VerticalPageBreakRecord.sid: - retval = new VerticalPageBreakRecord( in); - break; + return new VerticalPageBreakRecord( in); case WriteProtectRecord.sid: - retval = new WriteProtectRecord( in); - break; + return new WriteProtectRecord( in); case FilePassRecord.sid: - retval = new FilePassRecord(in); - break; + return new FilePassRecord(in); case NoteRecord.sid: - retval = new NoteRecord( in ); - break; + return new NoteRecord( in ); case FileSharingRecord.sid: - retval = new FileSharingRecord( in ); - break; + return new FileSharingRecord( in ); case HyperlinkRecord.sid: - retval = new HyperlinkRecord( in ); - break; - default: - retval = new UnknownRecord( in ); + return new HyperlinkRecord( in ); } - return retval; + return new UnknownRecord( in ); } /** * Method setDump - hex dump out data or not. - * - *@param dump */ - public void setDump(boolean dump) { this.dump = dump; } @@ -552,33 +393,44 @@ public class BiffViewer { * */ public static void main(String[] args) { + + System.setProperty("poi.deserialize.escher", "true"); + + if (args.length == 0) { + System.out.println( "Biff viewer needs a filename" ); + return; + } + try { - System.setProperty("poi.deserialize.escher", "true"); - - if (args.length == 0) - { - System.out.println( "Biff viewer needs a filename" ); + String inFileName = args[0]; + File inputFile = new File(inFileName); + if(!inputFile.exists()) { + throw new RuntimeException("specified inputFile '" + inFileName + "' does not exist"); } - else - { - BiffViewer viewer = new BiffViewer(args); - if ((args.length > 1) && args[1].equals("on")) { - viewer.setDump(true); - } - if ((args.length > 1) && args[1].equals("bfd")) { - POIFSFileSystem fs = - new POIFSFileSystem(new FileInputStream(args[0])); - InputStream stream = - fs.createDocumentInputStream("Workbook"); - int size = stream.available(); - byte[] data = new byte[size]; - - stream.read(data); - HexDump.dump(data, 0, System.out, 0); - } else { - viewer.run(); - } + PrintStream ps; + if (false) { // set to true to output to file + OutputStream os = new FileOutputStream(inFileName + ".out"); + ps = new PrintStream(os); + } else { + ps = System.out; } + BiffViewer viewer = new BiffViewer(inputFile, ps); + + if (args.length > 1 && args[1].equals("on")) { + viewer.setDump(true); + } + if (args.length > 1 && args[1].equals("bfd")) { + POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(inputFile)); + InputStream stream = fs.createDocumentInputStream("Workbook"); + int size = stream.available(); + byte[] data = new byte[size]; + + stream.read(data); + HexDump.dump(data, 0, System.out, 0); + } else { + viewer.run(); + } + ps.close(); } catch (Exception e) { e.printStackTrace(); } @@ -587,7 +439,7 @@ public class BiffViewer { /** * This record supports dumping of completed continue records. */ - static class RecordDetails + private static final class RecordDetails { short rectype, recsize; int startloc; @@ -616,18 +468,19 @@ public class BiffViewer { return record; } - public void dump() throws IOException - { - dumpNormal(record, startloc, rectype, recsize); + public void dump(PrintStream ps) { + ps.println("Offset 0x" + Integer.toHexString(startloc) + " (" + startloc + ")"); + ps.println( "recordid = 0x" + Integer.toHexString( rectype ) + ", size = " + recsize ); + ps.println( record.toString() ); } } - static class BiffviewRecordInputStream extends RecordInputStream { + private static final class BiffviewRecordInputStream extends RecordInputStream { public BiffviewRecordInputStream(InputStream in) { super(in); } - public void dumpBytes() { - HexDump.dump(this.data, 0, this.currentLength); + public void dumpBytes(PrintStream ps) { + ps.println(HexDump.dump(this.data, 0, this.currentLength)); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java index bd955f8254..31af2b773a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java @@ -140,4 +140,13 @@ public class HSSFName implements Name { } + /** + * Tests if this name points to a cell that no longer exists + * + * @return true if the name refers to a deleted cell, false otherwise + */ + public boolean isDeleted(){ + String ref = getReference(); + return "#REF!".endsWith(ref); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index b4c3cf3c7d..08ddb9656f 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -157,7 +157,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm protected HSSFWorkbook( Workbook book ) { - super(null, null); + super(null, null); workbook = book; sheets = new ArrayList( INITIAL_CAPACITY ); names = new ArrayList( INITIAL_CAPACITY ); @@ -775,14 +775,54 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm } /** - * removes sheet at the given index + * Removes sheet at the given index.

+ * + * Care must be taken if the removed sheet is the currently active or only selected sheet in + * the workbook. There are a few situations when Excel must have a selection and/or active + * sheet. (For example when printing - see Bug 40414).
+ * + * This method makes sure that if the removed sheet was active, another sheet will become + * active in its place. Furthermore, if the removed sheet was the only selected sheet, another + * sheet will become selected. The newly active/selected sheet will have the same index, or + * one less if the removed sheet was the last in the workbook. + * * @param index of the sheet (0-based) */ + public void removeSheetAt(int index) { + validateSheetIndex(index); + boolean wasActive = getSheetAt(index).isActive(); + boolean wasSelected = getSheetAt(index).isSelected(); - public void removeSheetAt(int index) - { sheets.remove(index); workbook.removeSheet(index); + + // set the remaining active/selected sheet + int nSheets = sheets.size(); + if (nSheets < 1) { + // nothing more to do if there are no sheets left + return; + } + // the index of the closest remaining sheet to the one just deleted + int newSheetIndex = index; + if (newSheetIndex >= nSheets) { + newSheetIndex = nSheets-1; + } + if (wasActive) { + setActiveSheet(newSheetIndex); + } + + if (wasSelected) { + boolean someOtherSheetIsStillSelected = false; + for (int i =0; i < nSheets; i++) { + if (getSheetAt(i).isSelected()) { + someOtherSheetIsStillSelected = true; + break; + } + } + if (!someOtherSheetIsStillSelected) { + setSelectedTab(newSheetIndex); + } + } } /** diff --git a/src/testcases/org/apache/poi/hssf/data/24207.xls b/src/testcases/org/apache/poi/hssf/data/24207.xls new file mode 100755 index 0000000000..eca56425ef Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/24207.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/36947.xls b/src/testcases/org/apache/poi/hssf/data/36947.xls new file mode 100755 index 0000000000..4c7bde6c2a Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/36947.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/39634.xls b/src/testcases/org/apache/poi/hssf/data/39634.xls new file mode 100755 index 0000000000..0f76bffe47 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/39634.xls differ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 2985278c54..0a55d65087 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -911,4 +911,45 @@ public final class TestBugs extends TestCase { writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); } + + /** + * Bug 21334: "File error: data may have been lost" with a file + * that contains macros and this formula: + * {=SUM(IF(FREQUENCY(IF(LEN(V4:V220)>0,MATCH(V4:V220,V4:V220,0),""),IF(LEN(V4:V220)>0,MATCH(V4:V220,V4:V220,0),""))>0,1))} + */ + public void test21334() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFCell cell = sh.createRow(0).createCell((short)0); + String formula = "SUM(IF(FREQUENCY(IF(LEN(V4:V220)>0,MATCH(V4:V220,V4:V220,0),\"\"),IF(LEN(V4:V220)>0,MATCH(V4:V220,V4:V220,0),\"\"))>0,1))"; + cell.setCellFormula(formula); + + HSSFWorkbook wb_sv = writeOutAndReadBack(wb); + HSSFCell cell_sv = wb_sv.getSheetAt(0).getRow(0).getCell((short)0); + assertEquals(formula, cell_sv.getCellFormula()); + } + + public void test36947() throws Exception { + HSSFWorkbook wb = openSample("36947.xls"); + assertTrue("no errors reading sample xls", true); + writeOutAndReadBack(wb); + assertTrue("no errors writing sample xls", true); + } + + /** + * Bug 42448: Can't parse SUMPRODUCT(A!C7:A!C67, B8:B68) / B69 + */ + public void test42448(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFCell cell = wb.createSheet().createRow(0).createCell((short)0); + cell.setCellFormula("SUMPRODUCT(A!C7:A!C67, B8:B68) / B69"); + assertTrue("no errors parsing formula", true); + } + + public void test39634() throws Exception { + HSSFWorkbook wb = openSample("39634.xls"); + assertTrue("no errors reading sample xls", true); + writeOutAndReadBack(wb); + assertTrue("no errors writing sample xls", true); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java index e1afb453bc..cb5b3d3554 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java @@ -232,11 +232,11 @@ public final class TestHSSFWorkbook extends TestCase { // Demonstrate bug 44525: // Well... not quite, since isActive + isSelected were also added in the same bug fix if (sheet1.isSelected()) { - throw new AssertionFailedError("Identified bug 44525 a"); + throw new AssertionFailedError("Identified bug 44523 a"); } wb.setActiveSheet(1); if (sheet1.isActive()) { - throw new AssertionFailedError("Identified bug 44525 b"); + throw new AssertionFailedError("Identified bug 44523 b"); } confirmActiveSelected(sheet1, false); @@ -299,8 +299,81 @@ public final class TestHSSFWorkbook extends TestCase { } } + + public void testActiveSheetAfterDelete_bug40414() { + HSSFWorkbook wb=new HSSFWorkbook(); + HSSFSheet sheet0 = wb.createSheet("Sheet0"); + HSSFSheet sheet1 = wb.createSheet("Sheet1"); + HSSFSheet sheet2 = wb.createSheet("Sheet2"); + HSSFSheet sheet3 = wb.createSheet("Sheet3"); + HSSFSheet sheet4 = wb.createSheet("Sheet4"); + + // confirm default activation/selection + confirmActiveSelected(sheet0, true); + confirmActiveSelected(sheet1, false); + confirmActiveSelected(sheet2, false); + confirmActiveSelected(sheet3, false); + confirmActiveSelected(sheet4, false); + + wb.setActiveSheet(3); + wb.setSelectedTab(3); + + confirmActiveSelected(sheet0, false); + confirmActiveSelected(sheet1, false); + confirmActiveSelected(sheet2, false); + confirmActiveSelected(sheet3, true); + confirmActiveSelected(sheet4, false); + + wb.removeSheetAt(3); + // after removing the only active/selected sheet, another should be active/selected in its place + if (!sheet4.isSelected()) { + throw new AssertionFailedError("identified bug 40414 a"); + } + if (!sheet4.isActive()) { + throw new AssertionFailedError("identified bug 40414 b"); + } + + confirmActiveSelected(sheet0, false); + confirmActiveSelected(sheet1, false); + confirmActiveSelected(sheet2, false); + confirmActiveSelected(sheet4, true); + + sheet3 = sheet4; // re-align local vars in this test case + + // Some more cases of removing sheets + + // Starting with a multiple selection, and different active sheet + wb.setSelectedTabs(new int[] { 1, 3, }); + wb.setActiveSheet(2); + confirmActiveSelected(sheet0, false, false); + confirmActiveSelected(sheet1, false, true); + confirmActiveSelected(sheet2, true, false); + confirmActiveSelected(sheet3, false, true); + + // removing a sheet that is not active, and not the only selected sheet + wb.removeSheetAt(3); + confirmActiveSelected(sheet0, false, false); + confirmActiveSelected(sheet1, false, true); + confirmActiveSelected(sheet2, true, false); + + // removing the only selected sheet + wb.removeSheetAt(1); + confirmActiveSelected(sheet0, false, false); + confirmActiveSelected(sheet2, true, true); + + // The last remaining sheet should always be active+selected + wb.removeSheetAt(1); + confirmActiveSelected(sheet0, true, true); + } + private static void confirmActiveSelected(HSSFSheet sheet, boolean expected) { - assertEquals(expected, sheet.isActive()); - assertEquals(expected, sheet.isSelected()); + confirmActiveSelected(sheet, expected, expected); + } + + + private static void confirmActiveSelected(HSSFSheet sheet, + boolean expectedActive, boolean expectedSelected) { + assertEquals("active", expectedActive, sheet.isActive()); + assertEquals("selected", expectedSelected, sheet.isSelected()); } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java index 98500960da..b3d08bf724 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java @@ -533,4 +533,28 @@ public final class TestNamedRange extends TestCase { String contents = c.getStringCellValue(); assertEquals("Contents of cell retrieved by its named reference", contents, cvalue); } + + public void testDeletedReference() throws Exception { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("24207.xls"); + assertEquals(2, wb.getNumberOfNames()); + + HSSFName name1 = wb.getNameAt(0); + assertEquals("a", name1.getNameName()); + assertEquals("Sheet1!$A$1", name1.getReference()); + AreaReference ref1 = new AreaReference(name1.getReference()); + assertTrue("Successfully constructed first reference", true); + + HSSFName name2 = wb.getNameAt(1); + assertEquals("b", name2.getNameName()); + assertEquals("#REF!", name2.getReference()); + assertTrue(name2.isDeleted()); + try { + AreaReference ref2 = new AreaReference(name2.getReference()); + fail("attempt to supply an invalid reference to AreaReference constructor results in exception"); + } catch (Exception e){ + ; + } + + } + }