/* ====================================================================
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.xssf.usermodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLException;
import org.apache.poi.POIXMLProperties;
import org.apache.poi.ss.formula.SheetNameFormatter;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.util.*;
import org.apache.poi.xslf.usermodel.XSLFPictureData;
import org.apache.poi.xssf.model.*;
import org.apache.poi.xssf.usermodel.helpers.XSSFFormulaUtils;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
/**
* High level representation of a SpreadsheetML workbook. This is the first object most users
* will construct whether they are reading or writing a workbook. It is also the
* top level object for creating new sheets/etc.
*/
public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable
* This constructor is deprecated since POI-3.8 because it does not close
* the underlying .zip file stream. In short, there are two ways to open a OPC package:
*
* It should be noted, that (2) uses quite a bit more memory than (1), which
* doesn't need to hold the whole zip file in memory, and can take advantage
* of native methods.
*
* To construct a workbook from file use the
* {@link #XSSFWorkbook(org.apache.poi.openxml4j.opc.OPCPackage)} constructor:
* Package
object,
* see http://poi.apache.org/oxml4j/.
*
* Once you have finished working with the Workbook, you should close the package
* by calling pkg.close, to avoid leaving file handles open.
*
* Creating a XSSFWorkbook from a file-backed OPC Package has a lower memory
* footprint than an InputStream backed one.
*
* @param pkg the OpenXML4J OPC Package
object.
*/
public XSSFWorkbook(OPCPackage pkg) throws IOException {
super(pkg);
//build a tree of POIXMLDocumentParts, this workbook being the root
load(XSSFFactory.getInstance());
}
/**
* Constructs a XSSFWorkbook object, by buffering the whole stream into memory
* and then opening an {@link OPCPackage} object for it.
*
* Using an {@link InputStream} requires more memory than using a File, so
* if a {@link File} is available then you should instead do something like
*
*/
public XSSFWorkbook(InputStream is) throws IOException {
super(PackageHelper.open(is));
//build a tree of POIXMLDocumentParts, this workbook being the root
load(XSSFFactory.getInstance());
}
/**
* Constructs a XSSFWorkbook object given a file name.
*
*
* OPCPackage pkg = OPCPackage.open(path);
* XSSFWorkbook wb = new XSSFWorkbook(pkg);
* // work with the wb object
* ......
* pkg.close(); // gracefully closes the underlying zip file
*
*
*
*
* OPCPackage pkg = OPCPackage.open(path);
* XSSFWorkbook wb = new XSSFWorkbook(pkg);
* // work with the wb object
* ......
* pkg.close(); // gracefully closes the underlying zip file
*
* Note that Excel allows sheet names up to 31 chars in length but other applications * (such as OpenOffice) allow more. Some versions of Excel crash with names longer than 31 chars, * others - truncate such names to 31 character. *
** POI's SpreadsheetAPI silently truncates the input argument to 31 characters. * Example: * *
* Sheet sheet = workbook.createSheet("My very long sheet name which is longer than 31 chars"); // will be truncated
* assert 31 == sheet.getSheetName().length();
* assert "My very long sheet name which i" == sheet.getSheetName();
*
*
*
* Except the 31-character constraint, Excel applies some other rules:
* * Sheet name MUST be unique in the workbook and MUST NOT contain the any of the following characters: *
* See {@link org.apache.poi.ss.util.WorkbookUtil#createSafeSheetName(String nameProposal)} * for a safe way to create valid names *
* @param sheetname sheetname to set for the sheet. * @return Sheet representing the new sheet. * @throws IllegalArgumentException if the name is null or invalid * or workbook already contains a sheet with this name * @see org.apache.poi.ss.util.WorkbookUtil#createSafeSheetName(String nameProposal) */ public XSSFSheet createSheet(String sheetname) { if (sheetname == null) { throw new IllegalArgumentException("sheetName must not be null"); } if (containsSheet( sheetname, sheets.size() )) throw new IllegalArgumentException( "The workbook already contains a sheet of this name"); // YK: Mimic Excel and silently truncate sheet names longer than 31 characters if(sheetname.length() > 31) sheetname = sheetname.substring(0, 31); WorkbookUtil.validateSheetName(sheetname); CTSheet sheet = addSheet(sheetname); int sheetNumber = 1; for(XSSFSheet sh : sheets) sheetNumber = (int)Math.max(sh.sheet.getSheetId() + 1, sheetNumber); XSSFSheet wrapper = (XSSFSheet)createRelationship(XSSFRelation.WORKSHEET, XSSFFactory.getInstance(), sheetNumber); wrapper.sheet = sheet; sheet.setId(wrapper.getPackageRelationship().getId()); sheet.setSheetId(sheetNumber); if(sheets.size() == 0) wrapper.setSelected(true); sheets.add(wrapper); return wrapper; } protected XSSFDialogsheet createDialogsheet(String sheetname, CTDialogsheet dialogsheet) { XSSFSheet sheet = createSheet(sheetname); return new XSSFDialogsheet(sheet); } private CTSheet addSheet(String sheetname) { CTSheet sheet = workbook.getSheets().addNewSheet(); sheet.setName(sheetname); return sheet; } /** * Finds a font that matches the one with the supplied attributes */ public XSSFFont findFont(short boldWeight, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline) { return stylesSource.findFont(boldWeight, color, fontHeight, name, italic, strikeout, typeOffset, underline); } /** * Convenience method to get the active sheet. The active sheet is is the sheet * which is currently displayed when the workbook is viewed in Excel. * 'Selected' sheet(s) is a distinct concept. */ public int getActiveSheetIndex() { //activeTab (Active Sheet Index) Specifies an unsignedInt //that contains the index to the active sheet in this book view. return (int)workbook.getBookViews().getWorkbookViewArray(0).getActiveTab(); } /** * Gets all pictures from the Workbook. * * @return the list of pictures (a list of {@link XSSFPictureData} objects.) * @see #addPicture(byte[], int) */ public Listnull
if it does not exist
*/
public XSSFSheet getSheet(String name) {
for (XSSFSheet sheet : sheets) {
if (name.equalsIgnoreCase(sheet.getSheetName())) {
return sheet;
}
}
return null;
}
/**
* Get the XSSFSheet object at the given index.
*
* @param index of the sheet number (0-based physical & logical)
* @return XSSFSheet at the provided index
* @throws IllegalArgumentException if the index is out of range (index
* < 0 || index >= getNumberOfSheets()).
*/
public XSSFSheet getSheetAt(int index) {
validateSheetIndex(index);
return sheets.get(index);
}
/**
* Returns the index of the sheet by his name (case insensitive match)
*
* @param name the sheet name
* @return index of the sheet (0 based) or -1-1 if not found
*/
public int getSheetIndex(Sheet sheet) {
int idx = 0;
for(XSSFSheet sh : sheets){
if(sh == sheet) return idx;
idx++;
}
return -1;
}
/**
* Get the sheet name
*
* @param sheetIx Number
* @return Sheet name
*/
public String getSheetName(int sheetIx) {
validateSheetIndex(sheetIx);
return sheets.get(sheetIx).getSheetName();
}
/**
* Allows foreach loops:
*
* XSSFWorkbook wb = new XSSFWorkbook(package);
* for(XSSFSheet sheet : wb){
*
* }
*
*/
public Iterator* i.e. Reference = $A$1:$B$2 * @param sheetIndex Zero-based sheet index (0 Represents the first sheet to keep consistent with java) * @param reference Valid name Reference for the Print Area */ public void setPrintArea(int sheetIndex, String reference) { XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_AREA, sheetIndex); if (name == null) { name = createBuiltInName(XSSFName.BUILTIN_PRINT_AREA, sheetIndex); } //short externSheetIndex = getWorkbook().checkExternSheet(sheetIndex); //name.setExternSheetNumber(externSheetIndex); String[] parts = COMMA_PATTERN.split(reference); StringBuffer sb = new StringBuffer(32); for (int i = 0; i < parts.length; i++) { if(i>0) { sb.append(","); } SheetNameFormatter.appendFormat(sb, getSheetName(sheetIndex)); sb.append("!"); sb.append(parts[i]); } name.setRefersToFormula(sb.toString()); } /** * For the Convenience of Java Programmers maintaining pointers. * @see #setPrintArea(int, String) * @param sheetIndex Zero-based sheet index (0 = First Sheet) * @param startColumn Column to begin printarea * @param endColumn Column to end the printarea * @param startRow Row to begin the printarea * @param endRow Row to end the printarea */ public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { String reference=getReferencePrintArea(getSheetName(sheetIndex), startColumn, endColumn, startRow, endRow); setPrintArea(sheetIndex, reference); } /** * Sets the repeating rows and columns for a sheet. *
* To set just repeating columns: ** workbook.setRepeatingRowsAndColumns(0,0,1,-1,-1); ** To set just repeating rows: *
* workbook.setRepeatingRowsAndColumns(0,-1,-1,0,4); ** To remove all repeating rows and columns for a sheet. *
* workbook.setRepeatingRowsAndColumns(0,-1,-1,-1,-1); ** * @param sheetIndex 0 based index to sheet. * @param startColumn 0 based start of repeating columns. * @param endColumn 0 based end of repeating columns. * @param startRow 0 based start of repeating rows. * @param endRow 0 based end of repeating rows. * * @deprecated use {@link XSSFSheet#setRepeatingRows(CellRangeAddress)} * or {@link XSSFSheet#setRepeatingColumns(CellRangeAddress)} */ public void setRepeatingRowsAndColumns(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { XSSFSheet sheet = getSheetAt(sheetIndex); CellRangeAddress rows = null; CellRangeAddress cols = null; if (startRow != -1) { rows = new CellRangeAddress(startRow, endRow, -1, -1); } if (startColumn != -1) { cols = new CellRangeAddress(-1, -1, startColumn, endColumn); } sheet.setRepeatingRows(rows); sheet.setRepeatingColumns(cols); } private static String getReferencePrintArea(String sheetName, int startC, int endC, int startR, int endR) { //windows excel example: Sheet1!$C$3:$E$4 CellReference colRef = new CellReference(sheetName, startR, startC, true, true); CellReference colRef2 = new CellReference(sheetName, endR, endC, true, true); return "$" + colRef.getCellRefParts()[2] + "$" + colRef.getCellRefParts()[1] + ":$" + colRef2.getCellRefParts()[2] + "$" + colRef2.getCellRefParts()[1]; } XSSFName getBuiltInName(String builtInCode, int sheetNumber) { for (XSSFName name : namedRanges) { if (name.getNameName().equalsIgnoreCase(builtInCode) && name.getSheetIndex() == sheetNumber) { return name; } } return null; } /** * Generates a NameRecord to represent a built-in region * * @return a new NameRecord * @throws IllegalArgumentException if sheetNumber is invalid * @throws POIXMLException if such a name already exists in the workbook */ XSSFName createBuiltInName(String builtInName, int sheetNumber) { validateSheetIndex(sheetNumber); CTDefinedNames names = workbook.getDefinedNames() == null ? workbook.addNewDefinedNames() : workbook.getDefinedNames(); CTDefinedName nameRecord = names.addNewDefinedName(); nameRecord.setName(builtInName); nameRecord.setLocalSheetId(sheetNumber); XSSFName name = new XSSFName(nameRecord, this); for (XSSFName nr : namedRanges) { if (nr.equals(name)) throw new POIXMLException("Builtin (" + builtInName + ") already exists for sheet (" + sheetNumber + ")"); } namedRanges.add(name); return name; } /** * We only set one sheet as selected for compatibility with HSSF. */ public void setSelectedTab(int index) { for (int i = 0 ; i < sheets.size() ; ++i) { XSSFSheet sheet = sheets.get(i); sheet.setSelected(i == index); } } /** * Set the sheet name. * * @param sheetIndex sheet number (0 based) * @param sheetname the new sheet name * @throws IllegalArgumentException if the name is null or invalid * or workbook already contains a sheet with this name * @see #createSheet(String) * @see org.apache.poi.ss.util.WorkbookUtil#createSafeSheetName(String nameProposal) */ public void setSheetName(int sheetIndex, String sheetname) { validateSheetIndex(sheetIndex); // YK: Mimic Excel and silently truncate sheet names longer than 31 characters if(sheetname != null && sheetname.length() > 31) sheetname = sheetname.substring(0, 31); WorkbookUtil.validateSheetName(sheetname); if (containsSheet(sheetname, sheetIndex )) throw new IllegalArgumentException( "The workbook already contains a sheet of this name" ); XSSFFormulaUtils utils = new XSSFFormulaUtils(this); utils.updateSheetName(sheetIndex, sheetname); workbook.getSheets().getSheetArray(sheetIndex).setName(sheetname); } /** * sets the order of appearance for a given sheet. * * @param sheetname the name of the sheet to reorder * @param pos the position that we want to insert the sheet into (0 based) */ public void setSheetOrder(String sheetname, int pos) { int idx = getSheetIndex(sheetname); sheets.add(pos, sheets.remove(idx)); // Reorder CTSheets CTSheets ct = workbook.getSheets(); XmlObject cts = ct.getSheetArray(idx).copy(); workbook.getSheets().removeSheet(idx); CTSheet newcts = ct.insertNewSheet(pos); newcts.set(cts); //notify sheets for(int i=0; i < sheets.size(); i++) { sheets.get(i).sheet = ct.getSheetArray(i); } } /** * marshal named ranges from the {@link #namedRanges} collection to the underlying CTWorkbook bean */ private void saveNamedRanges(){ // Named ranges if(namedRanges.size() > 0) { CTDefinedNames names = CTDefinedNames.Factory.newInstance(); CTDefinedName[] nr = new CTDefinedName[namedRanges.size()]; int i = 0; for(XSSFName name : namedRanges) { nr[i] = name.getCTName(); i++; } names.setDefinedNameArray(nr); workbook.setDefinedNames(names); } else { if(workbook.isSetDefinedNames()) { workbook.unsetDefinedNames(); } } } private void saveCalculationChain(){ if(calcChain != null){ int count = calcChain.getCTCalcChain().sizeOfCArray(); if(count == 0){ removeRelation(calcChain); calcChain = null; } } } @Override protected void commit() throws IOException { saveNamedRanges(); saveCalculationChain(); XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); xmlOptions.setSaveSyntheticDocumentElement(new QName(CTWorkbook.type.getName().getNamespaceURI(), "workbook")); Map
* The default value is false, meaning that the workbook uses the 1900 date system, * where 1/1/1900 is the first day in the system.. *
* @return true if the date systems used in the workbook starts in 1904 */ protected boolean isDate1904(){ CTWorkbookPr workbookPr = workbook.getWorkbookPr(); return workbookPr != null && workbookPr.getDate1904(); } /** * Get the document's embedded files. */ public List* Note that a sheet could instead be set to be very hidden, which is different * ({@link #isSheetVeryHidden(int)}) *
* @param sheetIx Number * @returntrue
if sheet is hidden
*/
public boolean isSheetHidden(int sheetIx) {
validateSheetIndex(sheetIx);
CTSheet ctSheet = sheets.get(sheetIx).sheet;
return ctSheet.getState() == STSheetState.HIDDEN;
}
/**
* Check whether a sheet is very hidden.
* * This is different from the normal hidden status * ({@link #isSheetHidden(int)}) *
* @param sheetIx sheet index to check * @returntrue
if sheet is very hidden
*/
public boolean isSheetVeryHidden(int sheetIx) {
validateSheetIndex(sheetIx);
CTSheet ctSheet = sheets.get(sheetIx).sheet;
return ctSheet.getState() == STSheetState.VERY_HIDDEN;
}
/**
* Sets the visible state of this sheet.
*
* Calling setSheetHidden(sheetIndex, true)
is equivalent to
* setSheetHidden(sheetIndex, Workbook.SHEET_STATE_HIDDEN)
.
*
* Calling setSheetHidden(sheetIndex, false)
is equivalent to
* setSheetHidden(sheetIndex, Workbook.SHEET_STATE_VISIBLE)
.
*
Workbook
constants:
* Workbook.SHEET_STATE_VISIBLE
,
* Workbook.SHEET_STATE_HIDDEN
, or
* Workbook.SHEET_STATE_VERY_HIDDEN
.
* @throws IllegalArgumentException if the supplied sheet index or state is invalid
*/
public void setSheetHidden(int sheetIx, int state) {
validateSheetIndex(sheetIx);
WorkbookUtil.validateSheetState(state);
CTSheet ctSheet = sheets.get(sheetIx).sheet;
ctSheet.setState(STSheetState.Enum.forInt(state + 1));
}
/**
* Fired when a formula is deleted from this workbook,
* for example when calling cell.setCellFormula(null)
*
* @see XSSFCell#setCellFormula(String)
*/
protected void onDeleteFormula(XSSFCell cell){
if(calcChain != null) {
int sheetId = (int)cell.getSheet().sheet.getSheetId();
calcChain.removeItem(sheetId, cell.getReference());
}
}
/**
* Return the CalculationChain object for this workbook
* * The calculation chain object specifies the order in which the cells in a workbook were last calculated *
* * @return theCalculationChain
object or null
if not defined
*/
@Internal
public CalculationChain getCalculationChain(){
return calcChain;
}
/**
*
* @return a collection of custom XML mappings defined in this workbook
*/
public Collection* The default instance extends the built-in functions with the Excel Analysis Tool Pack. * To set / evaluate custom functions you need to register them as follows: * * * *
* @return wrapped instance of UDFFinder that allows seeking functions both by index and name */ /*package*/ UDFFinder getUDFFinder() { return _udfFinder; } /** * Register a new toolpack in this workbook. * * @param toopack the toolpack to register */ public void addToolPack(UDFFinder toopack){ _udfFinder.add(toopack); } /** * Whether the application shall perform a full recalculation when the workbook is opened. ** Typically you want to force formula recalculation when you modify cell formulas or values * of a workbook previously created by Excel. When set to true, this flag will tell Excel * that it needs to recalculate all formulas in the workbook the next time the file is opened. *
** Note, that recalculation updates cached formula results and, thus, modifies the workbook. * Depending on the version, Excel may prompt you with "Do you want to save the changes in filename?" * on close. *
* * @param value true if the application will perform a full recalculation of * workbook values when the workbook is opened * @since 3.8 */ public void setForceFormulaRecalculation(boolean value){ CTWorkbook ctWorkbook = getCTWorkbook(); CTCalcPr calcPr = ctWorkbook.isSetCalcPr() ? ctWorkbook.getCalcPr() : ctWorkbook.addNewCalcPr(); // when set to 0, will tell Excel that it needs to recalculate all formulas // in the workbook the next time the file is opened. calcPr.setCalcId(0); if(value && calcPr.getCalcMode() == STCalcMode.MANUAL) { calcPr.setCalcMode(STCalcMode.AUTO); } } /** * Whether Excel will be asked to recalculate all formulas when the workbook is opened. * * @since 3.8 */ public boolean getForceFormulaRecalculation(){ CTWorkbook ctWorkbook = getCTWorkbook(); CTCalcPr calcPr = ctWorkbook.getCalcPr(); return calcPr != null && calcPr.getCalcId() != 0; } }