Inspired by bug #44958 - Record level support for Data Tables. (No formula parser...
[poi.git] / src / java / org / apache / poi / hssf / usermodel / HSSFSheet.java
blob2b6ad41397b643ce044c4d6a7cafab1c6d18e523
1 /* ====================================================================
2 Licensed to the Apache Software Foundation (ASF) under one or more
3 contributor license agreements. See the NOTICE file distributed with
4 this work for additional information regarding copyright ownership.
5 The ASF licenses this file to You under the Apache License, Version 2.0
6 (the "License"); you may not use this file except in compliance with
7 the License. You may obtain a copy of the License at
9 http://www.apache.org/licenses/LICENSE-2.0
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==================================================================== */
18 package org.apache.poi.hssf.usermodel;
20 import java.awt.font.FontRenderContext;
21 import java.awt.font.TextAttribute;
22 import java.awt.font.TextLayout;
23 import java.awt.geom.AffineTransform;
24 import java.io.PrintWriter;
25 import java.text.AttributedString;
26 import java.text.DecimalFormat;
27 import java.text.NumberFormat;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Stack;
32 import java.util.TreeMap;
34 import org.apache.poi.ddf.EscherRecord;
35 import org.apache.poi.hssf.model.FormulaParser;
36 import org.apache.poi.hssf.model.Sheet;
37 import org.apache.poi.hssf.model.Workbook;
38 import org.apache.poi.hssf.record.*;
39 import org.apache.poi.hssf.record.formula.Ptg;
40 import org.apache.poi.hssf.record.formula.RefPtg;
41 import org.apache.poi.hssf.util.HSSFCellRangeAddress;
42 import org.apache.poi.hssf.util.HSSFDataValidation;
43 import org.apache.poi.hssf.util.PaneInformation;
44 import org.apache.poi.hssf.util.Region;
45 import org.apache.poi.util.POILogFactory;
46 import org.apache.poi.util.POILogger;
48 /**
49 * High level representation of a worksheet.
50 * @author Andrew C. Oliver (acoliver at apache dot org)
51 * @author Glen Stampoultzis (glens at apache.org)
52 * @author Libin Roman (romal at vistaportal.com)
53 * @author Shawn Laubach (slaubach at apache dot org) (Just a little)
54 * @author Jean-Pierre Paris (jean-pierre.paris at m4x dot org) (Just a little, too)
55 * @author Yegor Kozlov (yegor at apache.org) (Autosizing columns)
57 public final class HSSFSheet {
58 private static final int DEBUG = POILogger.DEBUG;
60 /* Constants for margins */
61 public static final short LeftMargin = Sheet.LeftMargin;
62 public static final short RightMargin = Sheet.RightMargin;
63 public static final short TopMargin = Sheet.TopMargin;
64 public static final short BottomMargin = Sheet.BottomMargin;
66 public static final byte PANE_LOWER_RIGHT = (byte)0;
67 public static final byte PANE_UPPER_RIGHT = (byte)1;
68 public static final byte PANE_LOWER_LEFT = (byte)2;
69 public static final byte PANE_UPPER_LEFT = (byte)3;
72 /**
73 * Used for compile-time optimization. This is the initial size for the collection of
74 * rows. It is currently set to 20. If you generate larger sheets you may benefit
75 * by setting this to a higher number and recompiling a custom edition of HSSFSheet.
78 public final static int INITIAL_CAPACITY = 20;
80 /**
81 * reference to the low level Sheet object
84 private Sheet sheet;
85 private TreeMap rows;
86 protected Workbook book;
87 protected HSSFWorkbook workbook;
88 private int firstrow;
89 private int lastrow;
90 private static POILogger log = POILogFactory.getLogger(HSSFSheet.class);
92 /**
93 * Creates new HSSFSheet - called by HSSFWorkbook to create a sheet from
94 * scratch. You should not be calling this from application code (its protected anyhow).
96 * @param workbook - The HSSF Workbook object associated with the sheet.
97 * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createSheet()
100 protected HSSFSheet(HSSFWorkbook workbook)
102 sheet = Sheet.createSheet();
103 rows = new TreeMap(); // new ArrayList(INITIAL_CAPACITY);
104 this.workbook = workbook;
105 this.book = workbook.getWorkbook();
109 * Creates an HSSFSheet representing the given Sheet object. Should only be
110 * called by HSSFWorkbook when reading in an exisiting file.
112 * @param workbook - The HSSF Workbook object associated with the sheet.
113 * @param sheet - lowlevel Sheet object this sheet will represent
114 * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createSheet()
117 protected HSSFSheet(HSSFWorkbook workbook, Sheet sheet)
119 this.sheet = sheet;
120 rows = new TreeMap();
121 this.workbook = workbook;
122 this.book = workbook.getWorkbook();
123 setPropertiesFromSheet(sheet);
126 HSSFSheet cloneSheet(HSSFWorkbook workbook) {
127 return new HSSFSheet(workbook, sheet.cloneSheet());
132 * used internally to set the properties given a Sheet object
135 private void setPropertiesFromSheet(Sheet sheet)
137 int sloc = sheet.getLoc();
138 RowRecord row = sheet.getNextRow();
139 boolean rowRecordsAlreadyPresent = row!=null;
141 while (row != null)
143 createRowFromRecord(row);
145 row = sheet.getNextRow();
147 sheet.setLoc(sloc);
148 CellValueRecordInterface cval = sheet.getNextValueRecord();
149 long timestart = System.currentTimeMillis();
151 if (log.check( POILogger.DEBUG ))
152 log.log(DEBUG, "Time at start of cell creating in HSSF sheet = ",
153 new Long(timestart));
154 HSSFRow lastrow = null;
156 while (cval != null)
158 long cellstart = System.currentTimeMillis();
159 HSSFRow hrow = lastrow;
161 if ( ( lastrow == null ) || ( lastrow.getRowNum() != cval.getRow() ) )
163 hrow = getRow( cval.getRow() );
164 if (hrow == null) {
165 // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords
166 // Excel, OpenOffice.org and GoogleDocs are all OK with this, so POI should be too.
167 if (rowRecordsAlreadyPresent) {
168 // if at least one row record is present, all should be present.
169 throw new RuntimeException("Unexpected missing row when some rows already present");
171 // create the row record on the fly now.
172 RowRecord rowRec = new RowRecord(cval.getRow());
173 sheet.addRow(rowRec);
174 hrow = createRowFromRecord(rowRec);
177 if ( hrow != null )
179 lastrow = hrow;
180 if (log.check( POILogger.DEBUG ))
181 log.log( DEBUG, "record id = " + Integer.toHexString( ( (Record) cval ).getSid() ) );
182 hrow.createCellFromRecord( cval );
183 cval = sheet.getNextValueRecord();
184 if (log.check( POILogger.DEBUG ))
185 log.log( DEBUG, "record took ",
186 new Long( System.currentTimeMillis() - cellstart ) );
188 else
190 cval = null;
193 if (log.check( POILogger.DEBUG ))
194 log.log(DEBUG, "total sheet cell creation took ",
195 new Long(System.currentTimeMillis() - timestart));
199 * Create a new row within the sheet and return the high level representation
201 * @param rownum row number
202 * @return High level HSSFRow object representing a row in the sheet
203 * @see org.apache.poi.hssf.usermodel.HSSFRow
204 * @see #removeRow(HSSFRow)
206 public HSSFRow createRow(int rownum)
208 HSSFRow row = new HSSFRow(workbook, sheet, rownum);
210 addRow(row, true);
211 return row;
215 * Used internally to create a high level Row object from a low level row object.
216 * USed when reading an existing file
217 * @param row low level record to represent as a high level Row and add to sheet
218 * @return HSSFRow high level representation
221 private HSSFRow createRowFromRecord(RowRecord row)
223 HSSFRow hrow = new HSSFRow(workbook, sheet, row);
225 addRow(hrow, false);
226 return hrow;
230 * Remove a row from this sheet. All cells contained in the row are removed as well
232 * @param row representing a row to remove.
235 public void removeRow(HSSFRow row)
237 sheet.setLoc(sheet.getDimsLoc());
238 if (rows.size() > 0)
240 rows.remove(row);
241 if (row.getRowNum() == getLastRowNum())
243 lastrow = findLastRow(lastrow);
245 if (row.getRowNum() == getFirstRowNum())
247 firstrow = findFirstRow(firstrow);
249 Iterator iter = row.cellIterator();
251 while (iter.hasNext())
253 HSSFCell cell = (HSSFCell) iter.next();
255 sheet.removeValueRecord(row.getRowNum(),
256 cell.getCellValueRecord());
258 sheet.removeRow(row.getRowRecord());
263 * used internally to refresh the "last row" when the last row is removed.
265 private int findLastRow(int lastrow) {
266 if (lastrow < 1) {
267 return -1;
269 int rownum = lastrow - 1;
270 HSSFRow r = getRow(rownum);
272 while (r == null && rownum > 0) {
273 r = getRow(--rownum);
275 if (r == null) {
276 return -1;
278 return rownum;
282 * used internally to refresh the "first row" when the first row is removed.
285 private int findFirstRow(int firstrow)
287 int rownum = firstrow + 1;
288 HSSFRow r = getRow(rownum);
290 while (r == null && rownum <= getLastRowNum())
292 r = getRow(++rownum);
295 if (rownum > getLastRowNum())
296 return -1;
298 return rownum;
302 * add a row to the sheet
304 * @param addLow whether to add the row to the low level model - false if its already there
307 private void addRow(HSSFRow row, boolean addLow)
309 rows.put(row, row);
310 if (addLow)
312 sheet.addRow(row.getRowRecord());
314 if (row.getRowNum() > getLastRowNum())
316 lastrow = row.getRowNum();
318 if (row.getRowNum() < getFirstRowNum())
320 firstrow = row.getRowNum();
325 * Returns the logical row (not physical) 0-based. If you ask for a row that is not
326 * defined you get a null. This is to say row 4 represents the fifth row on a sheet.
327 * @param rownum row to get
328 * @return HSSFRow representing the rownumber or null if its not defined on the sheet
331 public HSSFRow getRow(int rownum)
333 HSSFRow row = new HSSFRow();
335 //row.setRowNum((short) rownum);
336 row.setRowNum( rownum);
337 return (HSSFRow) rows.get(row);
341 * Returns the number of phsyically defined rows (NOT the number of rows in the sheet)
344 public int getPhysicalNumberOfRows()
346 return rows.size();
350 * Gets the first row on the sheet
351 * @return the number of the first logical row on the sheet, zero based
353 public int getFirstRowNum()
355 return firstrow;
359 * Gets the number last row on the sheet.
360 * Owing to idiosyncrasies in the excel file
361 * format, if the result of calling this method
362 * is zero, you can't tell if that means there
363 * are zero rows on the sheet, or one at
364 * position zero. For that case, additionally
365 * call {@link #getPhysicalNumberOfRows()} to
366 * tell if there is a row at position zero
367 * or not.
368 * @return the number of the last row contained in this sheet, zero based.
371 public int getLastRowNum()
373 return lastrow;
377 * Creates a data validation object
378 * @param obj_validation The Data validation object settings
380 public void addValidationData(HSSFDataValidation obj_validation)
382 if ( obj_validation == null )
384 return;
386 DVALRecord dvalRec = (DVALRecord)sheet.findFirstRecordBySid( DVALRecord.sid );
387 int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
388 if ( dvalRec == null )
390 dvalRec = new DVALRecord();
391 sheet.getRecords().add( eofLoc, dvalRec );
393 int curr_dvRecNo = dvalRec.getDVRecNo();
394 dvalRec.setDVRecNo(curr_dvRecNo+1);
396 //create dv record
397 DVRecord dvRecord = new DVRecord();
399 //dv record's option flags
400 dvRecord.setDataType( obj_validation.getDataValidationType() );
401 dvRecord.setErrorStyle(obj_validation.getErrorStyle());
402 dvRecord.setEmptyCellAllowed(obj_validation.getEmptyCellAllowed());
403 dvRecord.setSurppresDropdownArrow(obj_validation.getSurppressDropDownArrow());
404 dvRecord.setShowPromptOnCellSelected(obj_validation.getShowPromptBox());
405 dvRecord.setShowErrorOnInvalidValue(obj_validation.getShowErrorBox());
406 dvRecord.setConditionOperator(obj_validation.getOperator());
408 //string fields
409 dvRecord.setStringField( DVRecord.STRING_PROMPT_TITLE,obj_validation.getPromptBoxTitle());
410 dvRecord.setStringField( DVRecord.STRING_PROMPT_TEXT, obj_validation.getPromptBoxText());
411 dvRecord.setStringField( DVRecord.STRING_ERROR_TITLE, obj_validation.getErrorBoxTitle());
412 dvRecord.setStringField( DVRecord.STRING_ERROR_TEXT, obj_validation.getErrorBoxText());
414 //formula fields ( size and data )
415 String str_formula = obj_validation.getFirstFormula();
416 FormulaParser fp = new FormulaParser(str_formula, workbook);
417 fp.parse();
418 Stack ptg_arr = new Stack();
419 Ptg[] ptg = fp.getRPNPtg();
420 int size = 0;
421 for (int k = 0; k < ptg.length; k++)
423 if ( ptg[k] instanceof org.apache.poi.hssf.record.formula.AreaPtg )
425 //we should set ptgClass to Ptg.CLASS_REF and explicit formula string to false
426 ptg[k].setClass(Ptg.CLASS_REF);
427 obj_validation.setExplicitListFormula(false);
429 size += ptg[k].getSize();
430 ptg_arr.push(ptg[k]);
432 dvRecord.setFirstFormulaRPN(ptg_arr);
433 dvRecord.setFirstFormulaSize((short)size);
435 dvRecord.setListExplicitFormula(obj_validation.getExplicitListFormula());
437 if ( obj_validation.getSecondFormula() != null )
439 str_formula = obj_validation.getSecondFormula();
440 fp = new FormulaParser(str_formula, workbook);
441 fp.parse();
442 ptg_arr = new Stack();
443 ptg = fp.getRPNPtg();
444 size = 0;
445 for (int k = 0; k < ptg.length; k++)
447 size += ptg[k].getSize();
448 ptg_arr.push(ptg[k]);
450 dvRecord.setSecFormulaRPN(ptg_arr);
451 dvRecord.setSecFormulaSize((short)size);
454 //dv records cell range field
455 HSSFCellRangeAddress cell_range = new HSSFCellRangeAddress();
456 cell_range.addADDRStructure(obj_validation.getFirstRow(), obj_validation.getFirstColumn(), obj_validation.getLastRow(), obj_validation.getLastColumn());
457 dvRecord.setCellRangeAddress(cell_range);
459 //add dv record
460 eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
461 sheet.getRecords().add( eofLoc, dvRecord );
465 * Get the visibility state for a given column.
466 * @param column - the column to get (0-based)
467 * @param hidden - the visiblity state of the column
470 public void setColumnHidden(short column, boolean hidden)
472 sheet.setColumnHidden(column, hidden);
476 * Get the hidden state for a given column.
477 * @param column - the column to set (0-based)
478 * @return hidden - the visiblity state of the column
481 public boolean isColumnHidden(short column)
483 return sheet.isColumnHidden(column);
487 * set the width (in units of 1/256th of a character width)
488 * @param column - the column to set (0-based)
489 * @param width - the width in units of 1/256th of a character width
492 public void setColumnWidth(short column, short width)
494 sheet.setColumnWidth(column, width);
498 * get the width (in units of 1/256th of a character width )
499 * @param column - the column to set (0-based)
500 * @return width - the width in units of 1/256th of a character width
503 public short getColumnWidth(short column)
505 return sheet.getColumnWidth(column);
509 * get the default column width for the sheet (if the columns do not define their own width) in
510 * characters
511 * @return default column width
514 public short getDefaultColumnWidth()
516 return sheet.getDefaultColumnWidth();
520 * get the default row height for the sheet (if the rows do not define their own height) in
521 * twips (1/20 of a point)
522 * @return default row height
525 public short getDefaultRowHeight()
527 return sheet.getDefaultRowHeight();
531 * get the default row height for the sheet (if the rows do not define their own height) in
532 * points.
533 * @return default row height in points
536 public float getDefaultRowHeightInPoints()
538 return (sheet.getDefaultRowHeight() / 20);
542 * set the default column width for the sheet (if the columns do not define their own width) in
543 * characters
544 * @param width default column width
547 public void setDefaultColumnWidth(short width)
549 sheet.setDefaultColumnWidth(width);
553 * set the default row height for the sheet (if the rows do not define their own height) in
554 * twips (1/20 of a point)
555 * @param height default row height
558 public void setDefaultRowHeight(short height)
560 sheet.setDefaultRowHeight(height);
564 * set the default row height for the sheet (if the rows do not define their own height) in
565 * points
566 * @param height default row height
569 public void setDefaultRowHeightInPoints(float height)
571 sheet.setDefaultRowHeight((short) (height * 20));
575 * get whether gridlines are printed.
576 * @return true if printed
579 public boolean isGridsPrinted()
581 return sheet.isGridsPrinted();
585 * set whether gridlines printed.
586 * @param value false if not printed.
589 public void setGridsPrinted(boolean value)
591 sheet.setGridsPrinted(value);
595 * adds a merged region of cells (hence those cells form one)
596 * @param region (rowfrom/colfrom-rowto/colto) to merge
597 * @return index of this region
600 public int addMergedRegion(Region region)
602 //return sheet.addMergedRegion((short) region.getRowFrom(),
603 return sheet.addMergedRegion( region.getRowFrom(),
604 region.getColumnFrom(),
605 //(short) region.getRowTo(),
606 region.getRowTo(),
607 region.getColumnTo());
611 * Whether a record must be inserted or not at generation to indicate that
612 * formula must be recalculated when workbook is opened.
613 * @param value true if an uncalced record must be inserted or not at generation
615 public void setForceFormulaRecalculation(boolean value)
617 sheet.setUncalced(value);
620 * Whether a record must be inserted or not at generation to indicate that
621 * formula must be recalculated when workbook is opened.
622 * @return true if an uncalced record must be inserted or not at generation
624 public boolean getForceFormulaRecalculation()
626 return sheet.getUncalced();
631 * determines whether the output is vertically centered on the page.
632 * @param value true to vertically center, false otherwise.
635 public void setVerticallyCenter(boolean value)
637 VCenterRecord record =
638 (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);
640 record.setVCenter(value);
644 * TODO: Boolean not needed, remove after next release
645 * @deprecated use getVerticallyCenter() instead
647 public boolean getVerticallyCenter(boolean value) {
648 return getVerticallyCenter();
652 * Determine whether printed output for this sheet will be vertically centered.
654 public boolean getVerticallyCenter()
656 VCenterRecord record =
657 (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);
659 return record.getVCenter();
663 * determines whether the output is horizontally centered on the page.
664 * @param value true to horizontally center, false otherwise.
667 public void setHorizontallyCenter(boolean value)
669 HCenterRecord record =
670 (HCenterRecord) sheet.findFirstRecordBySid(HCenterRecord.sid);
672 record.setHCenter(value);
676 * Determine whether printed output for this sheet will be horizontally centered.
679 public boolean getHorizontallyCenter()
681 HCenterRecord record =
682 (HCenterRecord) sheet.findFirstRecordBySid(HCenterRecord.sid);
684 return record.getHCenter();
690 * removes a merged region of cells (hence letting them free)
691 * @param index of the region to unmerge
694 public void removeMergedRegion(int index)
696 sheet.removeMergedRegion(index);
700 * returns the number of merged regions
701 * @return number of merged regions
704 public int getNumMergedRegions()
706 return sheet.getNumMergedRegions();
710 * gets the region at a particular index
711 * @param index of the region to fetch
712 * @return the merged region (simple eh?)
715 public Region getMergedRegionAt(int index)
717 return new Region(sheet.getMergedRegionAt(index));
721 * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not
722 * be the third row if say for instance the second row is undefined.
723 * Call getRowNum() on each row if you care which one it is.
725 public Iterator rowIterator()
727 return rows.values().iterator();
730 * Alias for {@link #rowIterator()} to allow
731 * foreach loops
733 public Iterator iterator() {
734 return rowIterator();
739 * used internally in the API to get the low level Sheet record represented by this
740 * Object.
741 * @return Sheet - low level representation of this HSSFSheet.
744 protected Sheet getSheet()
746 return sheet;
750 * whether alternate expression evaluation is on
751 * @param b alternative expression evaluation or not
754 public void setAlternativeExpression(boolean b)
756 WSBoolRecord record =
757 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
759 record.setAlternateExpression(b);
763 * whether alternative formula entry is on
764 * @param b alternative formulas or not
767 public void setAlternativeFormula(boolean b)
769 WSBoolRecord record =
770 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
772 record.setAlternateFormula(b);
776 * show automatic page breaks or not
777 * @param b whether to show auto page breaks
780 public void setAutobreaks(boolean b)
782 WSBoolRecord record =
783 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
785 record.setAutobreaks(b);
789 * set whether sheet is a dialog sheet or not
790 * @param b isDialog or not
793 public void setDialog(boolean b)
795 WSBoolRecord record =
796 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
798 record.setDialog(b);
802 * set whether to display the guts or not
804 * @param b guts or no guts (or glory)
807 public void setDisplayGuts(boolean b)
809 WSBoolRecord record =
810 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
812 record.setDisplayGuts(b);
816 * fit to page option is on
817 * @param b fit or not
820 public void setFitToPage(boolean b)
822 WSBoolRecord record =
823 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
825 record.setFitToPage(b);
829 * set if row summaries appear below detail in the outline
830 * @param b below or not
833 public void setRowSumsBelow(boolean b)
835 WSBoolRecord record =
836 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
838 record.setRowSumsBelow(b);
842 * set if col summaries appear right of the detail in the outline
843 * @param b right or not
846 public void setRowSumsRight(boolean b)
848 WSBoolRecord record =
849 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
851 record.setRowSumsRight(b);
855 * whether alternate expression evaluation is on
856 * @return alternative expression evaluation or not
859 public boolean getAlternateExpression()
861 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
862 .getAlternateExpression();
866 * whether alternative formula entry is on
867 * @return alternative formulas or not
870 public boolean getAlternateFormula()
872 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
873 .getAlternateFormula();
877 * show automatic page breaks or not
878 * @return whether to show auto page breaks
881 public boolean getAutobreaks()
883 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
884 .getAutobreaks();
888 * get whether sheet is a dialog sheet or not
889 * @return isDialog or not
892 public boolean getDialog()
894 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
895 .getDialog();
899 * get whether to display the guts or not
901 * @return guts or no guts (or glory)
904 public boolean getDisplayGuts()
906 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
907 .getDisplayGuts();
911 * fit to page option is on
912 * @return fit or not
915 public boolean getFitToPage()
917 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
918 .getFitToPage();
922 * get if row summaries appear below detail in the outline
923 * @return below or not
926 public boolean getRowSumsBelow()
928 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
929 .getRowSumsBelow();
933 * get if col summaries appear right of the detail in the outline
934 * @return right or not
937 public boolean getRowSumsRight()
939 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
940 .getRowSumsRight();
944 * Returns whether gridlines are printed.
945 * @return Gridlines are printed
947 public boolean isPrintGridlines() {
948 return getSheet().getPrintGridlines().getPrintGridlines();
952 * Turns on or off the printing of gridlines.
953 * @param newPrintGridlines boolean to turn on or off the printing of
954 * gridlines
956 public void setPrintGridlines( boolean newPrintGridlines )
958 getSheet().getPrintGridlines().setPrintGridlines( newPrintGridlines );
962 * Gets the print setup object.
963 * @return The user model for the print setup object.
965 public HSSFPrintSetup getPrintSetup()
967 return new HSSFPrintSetup( getSheet().getPrintSetup() );
971 * Gets the user model for the document header.
972 * @return The Document header.
974 public HSSFHeader getHeader()
976 return new HSSFHeader( getSheet().getHeader() );
980 * Gets the user model for the document footer.
981 * @return The Document footer.
983 public HSSFFooter getFooter()
985 return new HSSFFooter( getSheet().getFooter() );
989 * Note - this is not the same as whether the sheet is focused (isActive)
990 * @return <code>true</code> if this sheet is currently selected
992 public boolean isSelected() {
993 return getSheet().getWindowTwo().getSelected();
996 * Sets whether sheet is selected.
997 * @param sel Whether to select the sheet or deselect the sheet.
999 public void setSelected( boolean sel )
1001 getSheet().getWindowTwo().setSelected(sel);
1004 * @return <code>true</code> if this sheet is currently focused
1006 public boolean isActive() {
1007 return getSheet().getWindowTwo().isActive();
1010 * Sets whether sheet is selected.
1011 * @param sel Whether to select the sheet or deselect the sheet.
1013 public void setActive(boolean sel )
1015 getSheet().getWindowTwo().setActive(sel);
1019 * Gets the size of the margin in inches.
1020 * @param margin which margin to get
1021 * @return the size of the margin
1023 public double getMargin( short margin )
1025 return getSheet().getMargin( margin );
1029 * Sets the size of the margin in inches.
1030 * @param margin which margin to get
1031 * @param size the size of the margin
1033 public void setMargin( short margin, double size )
1035 getSheet().setMargin( margin, size );
1039 * Answer whether protection is enabled or disabled
1040 * @return true => protection enabled; false => protection disabled
1042 public boolean getProtect() {
1043 return getSheet().isProtected()[0];
1047 * @return hashed password
1049 public short getPassword() {
1050 return getSheet().getPassword().getPassword();
1054 * Answer whether object protection is enabled or disabled
1055 * @return true => protection enabled; false => protection disabled
1057 public boolean getObjectProtect() {
1058 return getSheet().isProtected()[1];
1062 * Answer whether scenario protection is enabled or disabled
1063 * @return true => protection enabled; false => protection disabled
1065 public boolean getScenarioProtect() {
1066 return getSheet().isProtected()[2];
1070 * Sets the protection on enabled or disabled
1071 * @param protect true => protection enabled; false => protection disabled
1072 * @deprecated use protectSheet(String, boolean, boolean)
1074 public void setProtect(boolean protect) {
1075 getSheet().getProtect().setProtect(protect);
1079 * Sets the protection enabled as well as the password
1080 * @param password to set for protection
1082 public void protectSheet(String password) {
1083 getSheet().protectSheet(password, true, true); //protect objs&scenarios(normal)
1087 * Sets the zoom magnication for the sheet. The zoom is expressed as a
1088 * fraction. For example to express a zoom of 75% use 3 for the numerator
1089 * and 4 for the denominator.
1091 * @param numerator The numerator for the zoom magnification.
1092 * @param denominator The denominator for the zoom magnification.
1094 public void setZoom( int numerator, int denominator)
1096 if (numerator < 1 || numerator > 65535)
1097 throw new IllegalArgumentException("Numerator must be greater than 1 and less than 65536");
1098 if (denominator < 1 || denominator > 65535)
1099 throw new IllegalArgumentException("Denominator must be greater than 1 and less than 65536");
1101 SCLRecord sclRecord = new SCLRecord();
1102 sclRecord.setNumerator((short)numerator);
1103 sclRecord.setDenominator((short)denominator);
1104 getSheet().setSCLRecord(sclRecord);
1108 * The top row in the visible view when the sheet is
1109 * first viewed after opening it in a viewer
1110 * @return short indicating the rownum (0 based) of the top row
1112 public short getTopRow()
1114 return sheet.getTopRow();
1118 * The left col in the visible view when the sheet is
1119 * first viewed after opening it in a viewer
1120 * @return short indicating the rownum (0 based) of the top row
1122 public short getLeftCol()
1124 return sheet.getLeftCol();
1128 * Sets desktop window pane display area, when the
1129 * file is first opened in a viewer.
1130 * @param toprow the top row to show in desktop window pane
1131 * @param leftcol the left column to show in desktop window pane
1133 public void showInPane(short toprow, short leftcol){
1134 this.sheet.setTopRow(toprow);
1135 this.sheet.setLeftCol(leftcol);
1139 * Shifts the merged regions left or right depending on mode
1140 * <p>
1141 * TODO: MODE , this is only row specific
1142 * @param startRow
1143 * @param endRow
1144 * @param n
1145 * @param isRow
1147 protected void shiftMerged(int startRow, int endRow, int n, boolean isRow) {
1148 List shiftedRegions = new ArrayList();
1149 //move merged regions completely if they fall within the new region boundaries when they are shifted
1150 for (int i = 0; i < this.getNumMergedRegions(); i++) {
1151 Region merged = this.getMergedRegionAt(i);
1153 boolean inStart = (merged.getRowFrom() >= startRow || merged.getRowTo() >= startRow);
1154 boolean inEnd = (merged.getRowTo() <= endRow || merged.getRowFrom() <= endRow);
1156 //dont check if it's not within the shifted area
1157 if (! (inStart && inEnd)) continue;
1159 //only shift if the region outside the shifted rows is not merged too
1160 if (!merged.contains(startRow-1, (short)0) && !merged.contains(endRow+1, (short)0)){
1161 merged.setRowFrom(merged.getRowFrom()+n);
1162 merged.setRowTo(merged.getRowTo()+n);
1163 //have to remove/add it back
1164 shiftedRegions.add(merged);
1165 this.removeMergedRegion(i);
1166 i = i -1; // we have to back up now since we removed one
1172 //readd so it doesn't get shifted again
1173 Iterator iterator = shiftedRegions.iterator();
1174 while (iterator.hasNext()) {
1175 Region region = (Region)iterator.next();
1177 this.addMergedRegion(region);
1183 * Shifts rows between startRow and endRow n number of rows.
1184 * If you use a negative number, it will shift rows up.
1185 * Code ensures that rows don't wrap around.
1187 * Calls shiftRows(startRow, endRow, n, false, false);
1189 * <p>
1190 * Additionally shifts merged regions that are completely defined in these
1191 * rows (ie. merged 2 cells on a row to be shifted).
1192 * @param startRow the row to start shifting
1193 * @param endRow the row to end shifting
1194 * @param n the number of rows to shift
1196 public void shiftRows( int startRow, int endRow, int n ) {
1197 shiftRows(startRow, endRow, n, false, false);
1201 * Shifts rows between startRow and endRow n number of rows.
1202 * If you use a negative number, it will shift rows up.
1203 * Code ensures that rows don't wrap around
1205 * <p>
1206 * Additionally shifts merged regions that are completely defined in these
1207 * rows (ie. merged 2 cells on a row to be shifted).
1208 * <p>
1209 * TODO Might want to add bounds checking here
1210 * @param startRow the row to start shifting
1211 * @param endRow the row to end shifting
1212 * @param n the number of rows to shift
1213 * @param copyRowHeight whether to copy the row height during the shift
1214 * @param resetOriginalRowHeight whether to set the original row's height to the default
1216 public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight)
1218 shiftRows(startRow, endRow, n, copyRowHeight, resetOriginalRowHeight, true);
1222 * Shifts rows between startRow and endRow n number of rows.
1223 * If you use a negative number, it will shift rows up.
1224 * Code ensures that rows don't wrap around
1226 * <p>
1227 * Additionally shifts merged regions that are completely defined in these
1228 * rows (ie. merged 2 cells on a row to be shifted).
1229 * <p>
1230 * TODO Might want to add bounds checking here
1231 * @param startRow the row to start shifting
1232 * @param endRow the row to end shifting
1233 * @param n the number of rows to shift
1234 * @param copyRowHeight whether to copy the row height during the shift
1235 * @param resetOriginalRowHeight whether to set the original row's height to the default
1236 * @param moveComments whether to move comments at the same time as the cells they are attached to
1238 public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments)
1240 int s, e, inc;
1241 if ( n < 0 )
1243 s = startRow;
1244 e = endRow;
1245 inc = 1;
1247 else
1249 s = endRow;
1250 e = startRow;
1251 inc = -1;
1254 shiftMerged(startRow, endRow, n, true);
1255 sheet.shiftRowBreaks(startRow, endRow, n);
1257 for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc )
1259 HSSFRow row = getRow( rowNum );
1260 HSSFRow row2Replace = getRow( rowNum + n );
1261 if ( row2Replace == null )
1262 row2Replace = createRow( rowNum + n );
1264 HSSFCell cell;
1266 // Remove all the old cells from the row we'll
1267 // be writing too, before we start overwriting
1268 // any cells. This avoids issues with cells
1269 // changing type, and records not being correctly
1270 // overwritten
1271 row2Replace.removeAllCells();
1273 // If this row doesn't exist, nothing needs to
1274 // be done for the now empty destination row
1275 if (row == null) continue; // Nothing to do for this row
1277 // Fetch the first and last columns of the
1278 // row now, so we still have them to hand
1279 // once we start removing cells
1280 short firstCol = row.getFirstCellNum();
1281 short lastCol = row.getLastCellNum();
1283 // Fix up row heights if required
1284 if (copyRowHeight) {
1285 row2Replace.setHeight(row.getHeight());
1287 if (resetOriginalRowHeight) {
1288 row.setHeight((short)0xff);
1291 // Copy each cell from the source row to
1292 // the destination row
1293 for(Iterator cells = row.cellIterator(); cells.hasNext(); ) {
1294 cell = (HSSFCell)cells.next();
1295 row.removeCell( cell );
1296 CellValueRecordInterface cellRecord = cell.getCellValueRecord();
1297 cellRecord.setRow( rowNum + n );
1298 row2Replace.createCellFromRecord( cellRecord );
1299 sheet.addValueRecord( rowNum + n, cellRecord );
1301 // Now zap all the cells in the source row
1302 row.removeAllCells();
1304 // Move comments from the source row to the
1305 // destination row. Note that comments can
1306 // exist for cells which are null
1307 if(moveComments) {
1308 for( short col = firstCol; col <= lastCol; col++ ) {
1309 HSSFComment comment = getCellComment(rowNum, col);
1310 if (comment != null) {
1311 comment.setRow(rowNum + n);
1316 if ( endRow == lastrow || endRow + n > lastrow ) lastrow = Math.min( endRow + n, 65535 );
1317 if ( startRow == firstrow || startRow + n < firstrow ) firstrow = Math.max( startRow + n, 0 );
1319 // Update any formulas on this sheet that point to
1320 // rows which have been moved
1321 updateFormulasAfterShift(startRow, endRow, n);
1325 * Called by shiftRows to update formulas on this sheet
1326 * to point to the new location of moved rows
1328 private void updateFormulasAfterShift(int startRow, int endRow, int n) {
1329 // Need to look at every cell on the sheet
1330 // Not just those that were moved
1331 Iterator ri = rowIterator();
1332 while(ri.hasNext()) {
1333 HSSFRow r = (HSSFRow)ri.next();
1334 Iterator ci = r.cellIterator();
1335 while(ci.hasNext()) {
1336 HSSFCell c = (HSSFCell)ci.next();
1337 if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
1338 // Since it's a formula cell, process the
1339 // formula string, and look to see if
1340 // it contains any references
1341 FormulaParser fp = new FormulaParser(c.getCellFormula(), workbook);
1342 fp.parse();
1344 // Look for references, and update if needed
1345 Ptg[] ptgs = fp.getRPNPtg();
1346 boolean changed = false;
1347 for(int i=0; i<ptgs.length; i++) {
1348 if(ptgs[i] instanceof RefPtg) {
1349 RefPtg rptg = (RefPtg)ptgs[i];
1350 if(startRow <= rptg.getRowAsInt() &&
1351 rptg.getRowAsInt() <= endRow) {
1352 // References a row that moved
1353 rptg.setRow(rptg.getRowAsInt() + n);
1354 changed = true;
1358 // If any references were changed, then
1359 // re-create the formula string
1360 if(changed) {
1361 c.setCellFormula(
1362 fp.toFormulaString(ptgs)
1370 protected void insertChartRecords( List records )
1372 int window2Loc = sheet.findFirstRecordLocBySid( WindowTwoRecord.sid );
1373 sheet.getRecords().addAll( window2Loc, records );
1377 * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
1378 * @param colSplit Horizonatal position of split.
1379 * @param rowSplit Vertical position of split.
1380 * @param topRow Top row visible in bottom pane
1381 * @param leftmostColumn Left column visible in right pane.
1383 public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow )
1385 if (colSplit < 0 || colSplit > 255) throw new IllegalArgumentException("Column must be between 0 and 255");
1386 if (rowSplit < 0 || rowSplit > 65535) throw new IllegalArgumentException("Row must be between 0 and 65535");
1387 if (leftmostColumn < colSplit) throw new IllegalArgumentException("leftmostColumn parameter must not be less than colSplit parameter");
1388 if (topRow < rowSplit) throw new IllegalArgumentException("topRow parameter must not be less than leftmostColumn parameter");
1389 getSheet().createFreezePane( colSplit, rowSplit, topRow, leftmostColumn );
1393 * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
1394 * @param colSplit Horizonatal position of split.
1395 * @param rowSplit Vertical position of split.
1397 public void createFreezePane( int colSplit, int rowSplit )
1399 createFreezePane( colSplit, rowSplit, colSplit, rowSplit );
1403 * Creates a split pane. Any existing freezepane or split pane is overwritten.
1404 * @param xSplitPos Horizonatal position of split (in 1/20th of a point).
1405 * @param ySplitPos Vertical position of split (in 1/20th of a point).
1406 * @param topRow Top row visible in bottom pane
1407 * @param leftmostColumn Left column visible in right pane.
1408 * @param activePane Active pane. One of: PANE_LOWER_RIGHT,
1409 * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT
1410 * @see #PANE_LOWER_LEFT
1411 * @see #PANE_LOWER_RIGHT
1412 * @see #PANE_UPPER_LEFT
1413 * @see #PANE_UPPER_RIGHT
1415 public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane )
1417 getSheet().createSplitPane( xSplitPos, ySplitPos, topRow, leftmostColumn, activePane );
1421 * Returns the information regarding the currently configured pane (split or freeze).
1422 * @return null if no pane configured, or the pane information.
1424 public PaneInformation getPaneInformation() {
1425 return getSheet().getPaneInformation();
1429 * Sets whether the gridlines are shown in a viewer.
1430 * @param show whether to show gridlines or not
1432 public void setDisplayGridlines(boolean show) {
1433 sheet.setDisplayGridlines(show);
1437 * Returns if gridlines are displayed.
1438 * @return whether gridlines are displayed
1440 public boolean isDisplayGridlines() {
1441 return sheet.isDisplayGridlines();
1445 * Sets whether the formulas are shown in a viewer.
1446 * @param show whether to show formulas or not
1448 public void setDisplayFormulas(boolean show) {
1449 sheet.setDisplayFormulas(show);
1453 * Returns if formulas are displayed.
1454 * @return whether formulas are displayed
1456 public boolean isDisplayFormulas() {
1457 return sheet.isDisplayFormulas();
1461 * Sets whether the RowColHeadings are shown in a viewer.
1462 * @param show whether to show RowColHeadings or not
1464 public void setDisplayRowColHeadings(boolean show) {
1465 sheet.setDisplayRowColHeadings(show);
1469 * Returns if RowColHeadings are displayed.
1470 * @return whether RowColHeadings are displayed
1472 public boolean isDisplayRowColHeadings() {
1473 return sheet.isDisplayRowColHeadings();
1477 * Sets a page break at the indicated row
1478 * @param row FIXME: Document this!
1480 public void setRowBreak(int row) {
1481 validateRow(row);
1482 sheet.setRowBreak(row, (short)0, (short)255);
1486 * Determines if there is a page break at the indicated row
1487 * @param row FIXME: Document this!
1488 * @return FIXME: Document this!
1490 public boolean isRowBroken(int row) {
1491 return sheet.isRowBroken(row);
1495 * Removes the page break at the indicated row
1496 * @param row
1498 public void removeRowBreak(int row) {
1499 sheet.removeRowBreak(row);
1503 * Retrieves all the horizontal page breaks
1504 * @return all the horizontal page breaks, or null if there are no row page breaks
1506 public int[] getRowBreaks(){
1507 //we can probably cache this information, but this should be a sparsely used function
1508 int count = sheet.getNumRowBreaks();
1509 if (count > 0) {
1510 int[] returnValue = new int[count];
1511 Iterator iterator = sheet.getRowBreaks();
1512 int i = 0;
1513 while (iterator.hasNext()) {
1514 PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next();
1515 returnValue[i++] = breakItem.main;
1517 return returnValue;
1519 return null;
1523 * Retrieves all the vertical page breaks
1524 * @return all the vertical page breaks, or null if there are no column page breaks
1526 public short[] getColumnBreaks(){
1527 //we can probably cache this information, but this should be a sparsely used function
1528 int count = sheet.getNumColumnBreaks();
1529 if (count > 0) {
1530 short[] returnValue = new short[count];
1531 Iterator iterator = sheet.getColumnBreaks();
1532 int i = 0;
1533 while (iterator.hasNext()) {
1534 PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next();
1535 returnValue[i++] = breakItem.main;
1537 return returnValue;
1539 return null;
1544 * Sets a page break at the indicated column
1545 * @param column
1547 public void setColumnBreak(short column) {
1548 validateColumn(column);
1549 sheet.setColumnBreak(column, (short)0, (short)65535);
1553 * Determines if there is a page break at the indicated column
1554 * @param column FIXME: Document this!
1555 * @return FIXME: Document this!
1557 public boolean isColumnBroken(short column) {
1558 return sheet.isColumnBroken(column);
1562 * Removes a page break at the indicated column
1563 * @param column
1565 public void removeColumnBreak(short column) {
1566 sheet.removeColumnBreak(column);
1570 * Runs a bounds check for row numbers
1571 * @param row
1573 protected void validateRow(int row) {
1574 if (row > 65535) throw new IllegalArgumentException("Maximum row number is 65535");
1575 if (row < 0) throw new IllegalArgumentException("Minumum row number is 0");
1579 * Runs a bounds check for column numbers
1580 * @param column
1582 protected void validateColumn(short column) {
1583 if (column > 255) throw new IllegalArgumentException("Maximum column number is 255");
1584 if (column < 0) throw new IllegalArgumentException("Minimum column number is 0");
1588 * Aggregates the drawing records and dumps the escher record hierarchy
1589 * to the standard output.
1591 public void dumpDrawingRecords(boolean fat)
1593 sheet.aggregateDrawingRecords(book.getDrawingManager(), false);
1595 EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid);
1596 List escherRecords = r.getEscherRecords();
1597 PrintWriter w = new PrintWriter(System.out);
1598 for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); )
1600 EscherRecord escherRecord = (EscherRecord) iterator.next();
1601 if (fat)
1602 System.out.println(escherRecord.toString());
1603 else
1604 escherRecord.display(w, 0);
1606 w.flush();
1610 * Creates the top-level drawing patriarch. This will have
1611 * the effect of removing any existing drawings on this
1612 * sheet.
1613 * This may then be used to add graphics or charts
1614 * @return The new patriarch.
1616 public HSSFPatriarch createDrawingPatriarch()
1618 // Create the drawing group if it doesn't already exist.
1619 book.createDrawingGroup();
1621 sheet.aggregateDrawingRecords(book.getDrawingManager(), true);
1622 EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
1623 HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
1624 agg.clear(); // Initially the behaviour will be to clear out any existing shapes in the sheet when
1625 // creating a new patriarch.
1626 agg.setPatriarch(patriarch);
1627 return patriarch;
1631 * Returns the agregate escher records for this sheet,
1632 * it there is one.
1633 * WARNING - calling this will trigger a parsing of the
1634 * associated escher records. Any that aren't supported
1635 * (such as charts and complex drawing types) will almost
1636 * certainly be lost or corrupted when written out.
1638 public EscherAggregate getDrawingEscherAggregate() {
1639 book.findDrawingGroup();
1641 // If there's now no drawing manager, then there's
1642 // no drawing escher records on the workbook
1643 if(book.getDrawingManager() == null) {
1644 return null;
1647 int found = sheet.aggregateDrawingRecords(
1648 book.getDrawingManager(), false
1650 if(found == -1) {
1651 // Workbook has drawing stuff, but this sheet doesn't
1652 return null;
1655 // Grab our aggregate record, and wire it up
1656 EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
1657 return agg;
1661 * Returns the top-level drawing patriach, if there is
1662 * one.
1663 * This will hold any graphics or charts for the sheet.
1664 * WARNING - calling this will trigger a parsing of the
1665 * associated escher records. Any that aren't supported
1666 * (such as charts and complex drawing types) will almost
1667 * certainly be lost or corrupted when written out. Only
1668 * use this with simple drawings, otherwise call
1669 * {@link HSSFSheet#createDrawingPatriarch()} and
1670 * start from scratch!
1672 public HSSFPatriarch getDrawingPatriarch() {
1673 EscherAggregate agg = getDrawingEscherAggregate();
1674 if(agg == null) return null;
1676 HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
1677 agg.setPatriarch(patriarch);
1679 // Have it process the records into high level objects
1680 // as best it can do (this step may eat anything
1681 // that isn't supported, you were warned...)
1682 agg.convertRecordsToUserModel();
1684 // Return what we could cope with
1685 return patriarch;
1689 * Expands or collapses a column group.
1691 * @param columnNumber One of the columns in the group.
1692 * @param collapsed true = collapse group, false = expand group.
1694 public void setColumnGroupCollapsed( short columnNumber, boolean collapsed )
1696 sheet.setColumnGroupCollapsed( columnNumber, collapsed );
1700 * Create an outline for the provided column range.
1702 * @param fromColumn beginning of the column range.
1703 * @param toColumn end of the column range.
1705 public void groupColumn(short fromColumn, short toColumn)
1707 sheet.groupColumnRange( fromColumn, toColumn, true );
1710 public void ungroupColumn( short fromColumn, short toColumn )
1712 sheet.groupColumnRange( fromColumn, toColumn, false );
1715 public void groupRow(int fromRow, int toRow)
1717 sheet.groupRowRange( fromRow, toRow, true );
1720 public void ungroupRow(int fromRow, int toRow)
1722 sheet.groupRowRange( fromRow, toRow, false );
1725 public void setRowGroupCollapsed( int row, boolean collapse )
1727 sheet.setRowGroupCollapsed( row, collapse );
1731 * Sets the default column style for a given column. POI will only apply this style to new cells added to the sheet.
1733 * @param column the column index
1734 * @param style the style to set
1736 public void setDefaultColumnStyle(short column, HSSFCellStyle style) {
1737 sheet.setColumn(column, new Short(style.getIndex()), null, null, null, null);
1741 * Adjusts the column width to fit the contents.
1743 * This process can be relatively slow on large sheets, so this should
1744 * normally only be called once per column, at the end of your
1745 * processing.
1747 * @param column the column index
1749 public void autoSizeColumn(short column) {
1750 autoSizeColumn(column, false);
1754 * Adjusts the column width to fit the contents.
1756 * This process can be relatively slow on large sheets, so this should
1757 * normally only be called once per column, at the end of your
1758 * processing.
1760 * You can specify whether the content of merged cells should be considered or ignored.
1761 * Default is to ignore merged cells.
1763 * @param column the column index
1764 * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
1766 public void autoSizeColumn(short column, boolean useMergedCells) {
1767 AttributedString str;
1768 TextLayout layout;
1770 * Excel measures columns in units of 1/256th of a character width
1771 * but the docs say nothing about what particular character is used.
1772 * '0' looks to be a good choice.
1774 char defaultChar = '0';
1777 * This is the multiple that the font height is scaled by when determining the
1778 * boundary of rotated text.
1780 double fontHeightMultiple = 2.0;
1782 FontRenderContext frc = new FontRenderContext(null, true, true);
1784 HSSFWorkbook wb = new HSSFWorkbook(book);
1785 HSSFFont defaultFont = wb.getFontAt((short) 0);
1787 str = new AttributedString("" + defaultChar);
1788 copyAttributes(defaultFont, str, 0, 1);
1789 layout = new TextLayout(str.getIterator(), frc);
1790 int defaultCharWidth = (int)layout.getAdvance();
1792 double width = -1;
1793 rows:
1794 for (Iterator it = rowIterator(); it.hasNext();) {
1795 HSSFRow row = (HSSFRow) it.next();
1796 HSSFCell cell = row.getCell(column);
1798 if (cell == null) continue;
1800 int colspan = 1;
1801 for (int i = 0 ; i < getNumMergedRegions(); i++) {
1802 if (getMergedRegionAt(i).contains(row.getRowNum(), column)) {
1803 if (!useMergedCells) {
1804 // If we're not using merged cells, skip this one and move on to the next.
1805 continue rows;
1807 cell = row.getCell(getMergedRegionAt(i).getColumnFrom());
1808 colspan = 1+ getMergedRegionAt(i).getColumnTo() - getMergedRegionAt(i).getColumnFrom();
1812 HSSFCellStyle style = cell.getCellStyle();
1813 HSSFFont font = wb.getFontAt(style.getFontIndex());
1815 if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
1816 HSSFRichTextString rt = cell.getRichStringCellValue();
1817 String[] lines = rt.getString().split("\\n");
1818 for (int i = 0; i < lines.length; i++) {
1819 String txt = lines[i] + defaultChar;
1820 str = new AttributedString(txt);
1821 copyAttributes(font, str, 0, txt.length());
1823 if (rt.numFormattingRuns() > 0) {
1824 for (int j = 0; j < lines[i].length(); j++) {
1825 int idx = rt.getFontAtIndex(j);
1826 if (idx != 0) {
1827 HSSFFont fnt = wb.getFontAt((short) idx);
1828 copyAttributes(fnt, str, j, j + 1);
1833 layout = new TextLayout(str.getIterator(), frc);
1834 if(style.getRotation() != 0){
1836 * Transform the text using a scale so that it's height is increased by a multiple of the leading,
1837 * and then rotate the text before computing the bounds. The scale results in some whitespace around
1838 * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
1839 * is added by the standard Excel autosize.
1841 AffineTransform trans = new AffineTransform();
1842 trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
1843 trans.concatenate(
1844 AffineTransform.getScaleInstance(1, fontHeightMultiple)
1846 width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1847 } else {
1848 width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1851 } else {
1852 String sval = null;
1853 if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) {
1854 HSSFDataFormat dataformat = wb.createDataFormat();
1855 short idx = style.getDataFormat();
1856 String format = dataformat.getFormat(idx).replaceAll("\"", "");
1857 double value = cell.getNumericCellValue();
1858 try {
1859 NumberFormat fmt;
1860 if ("General".equals(format))
1861 sval = "" + value;
1862 else
1864 fmt = new DecimalFormat(format);
1865 sval = fmt.format(value);
1867 } catch (Exception e) {
1868 sval = "" + value;
1870 } else if (cell.getCellType() == HSSFCell.CELL_TYPE_BOOLEAN) {
1871 sval = String.valueOf(cell.getBooleanCellValue());
1873 if(sval != null) {
1874 String txt = sval + defaultChar;
1875 str = new AttributedString(txt);
1876 copyAttributes(font, str, 0, txt.length());
1878 layout = new TextLayout(str.getIterator(), frc);
1879 if(style.getRotation() != 0){
1881 * Transform the text using a scale so that it's height is increased by a multiple of the leading,
1882 * and then rotate the text before computing the bounds. The scale results in some whitespace around
1883 * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
1884 * is added by the standard Excel autosize.
1886 AffineTransform trans = new AffineTransform();
1887 trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
1888 trans.concatenate(
1889 AffineTransform.getScaleInstance(1, fontHeightMultiple)
1891 width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1892 } else {
1893 width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1899 if (width != -1) {
1900 if (width > Short.MAX_VALUE) { //width can be bigger that Short.MAX_VALUE!
1901 width = Short.MAX_VALUE;
1903 sheet.setColumnWidth(column, (short) (width * 256));
1908 * Copy text attributes from the supplied HSSFFont to Java2D AttributedString
1910 private void copyAttributes(HSSFFont font, AttributedString str, int startIdx, int endIdx) {
1911 str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
1912 str.addAttribute(TextAttribute.SIZE, new Float(font.getFontHeightInPoints()));
1913 if (font.getBoldweight() == HSSFFont.BOLDWEIGHT_BOLD) str.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx);
1914 if (font.getItalic() ) str.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx);
1915 if (font.getUnderline() == HSSFFont.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
1919 * Returns cell comment for the specified row and column
1921 * @return cell comment or <code>null</code> if not found
1923 public HSSFComment getCellComment(int row, int column) {
1924 // Don't call findCellComment directly, otherwise
1925 // two calls to this method will result in two
1926 // new HSSFComment instances, which is bad
1927 HSSFRow r = getRow(row);
1928 if(r != null) {
1929 HSSFCell c = r.getCell((short)column);
1930 if(c != null) {
1931 return c.getCellComment();
1932 } else {
1933 // No cell, so you will get new
1934 // objects every time, sorry...
1935 return HSSFCell.findCellComment(sheet, row, column);
1938 return null;
1941 public HSSFSheetConditionalFormatting getSheetConditionalFormatting() {
1942 return new HSSFSheetConditionalFormatting(workbook, sheet);