/* ====================================================================
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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.hssf.record.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.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.CellReference;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.PackageHelper;
import org.apache.poi.xssf.model.CalculationChain;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
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
* 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);
namedRanges.add(name);
}
//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.
*
* The character count MUST be greater than or equal to 1 and less than or equal to 31.
* The string MUST NOT contain the any of the following characters:
* Package
object,
* see www.openxml4j.org.
*
* @param pkg the OpenXML4J Package
object.
*/
public XSSFWorkbook(OPCPackage pkg) throws IOException {
super(ensureWriteAccess(pkg));
//build a tree of POIXMLDocumentParts, this workbook being the root
try {
read(XSSFFactory.getInstance());
} catch (OpenXML4JException e){
throw new POIXMLException(e);
}
onDocumentRead();
}
/**
* Constructs a XSSFWorkbook object given a file name.
*
* @param path the file name.
*/
public XSSFWorkbook(String path) throws IOException {
this(openPackage(path));
}
@Override
protected void onDocumentRead() throws IOException {
try {
WorkbookDocument doc = WorkbookDocument.Factory.parse(getPackagePart().getInputStream());
this.workbook = doc.getWorkbook();
Mapnull
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:
*
*/
public Iterator
* XSSFWorkbook wb = new XSSFWorkbook(package);
* for(XSSFSheet sheet : wb){
*
* }
*
*
* 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);
XSSFSheet sheet = getSheetAt(index);
removeRelation(sheet);
this.sheets.remove(index);
this.workbook.getSheets().removeSheet(index);
}
/**
* Retrieves the current policy on what to do when
* getting missing or blank cells from a row.
* The default is to return blank and null cells.
* {@link MissingCellPolicy}
*/
public MissingCellPolicy getMissingCellPolicy() {
return _missingCellPolicy;
}
/**
* Sets the policy on what to do when
* getting missing or blank cells from a row.
* This will then apply to all calls to
* {@link Row#getCell(int)}}. See
* {@link MissingCellPolicy}
*/
public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) {
_missingCellPolicy = missingCellPolicy;
}
/**
* Convenience method to set 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 void setActiveSheet(int index) {
validateSheetIndex(index);
//activeTab (Active Sheet Index) Specifies an unsignedInt that contains the index to the active sheet in this book view.
CTBookView[] arrayBook = workbook.getBookViews().getWorkbookViewArray();
for (int i = 0; i < arrayBook.length; i++) {
workbook.getBookViews().getWorkbookViewArray(i).setActiveTab(index);
}
}
/**
* Validate sheet index
*
* @param index the index to validate
* @throws IllegalArgumentException if the index is out of range (index
* < 0 || index >= getNumberOfSheets()).
*/
private void validateSheetIndex(int index) {
int lastSheetIx = sheets.size() - 1;
if (index < 0 || index > lastSheetIx) {
throw new IllegalArgumentException("Sheet index ("
+ index +") is out of range (0.." + lastSheetIx + ")");
}
}
/**
* Gets the first tab that is displayed in the list of tabs in excel.
*
* @return integer that contains the index to the active sheet in this book view.
*/
public int getFirstVisibleTab() {
CTBookViews bookViews = workbook.getBookViews();
CTBookView bookView = bookViews.getWorkbookViewArray(0);
return (short) bookView.getActiveTab();
}
/**
* Sets the first tab that is displayed in the list of tabs in excel.
*
* @param index integer that contains the index to the active sheet in this book view.
*/
public void setFirstVisibleTab(int index) {
CTBookViews bookViews = workbook.getBookViews();
CTBookView bookView= bookViews.getWorkbookViewArray(0);
bookView.setActiveTab(index);
}
/**
* Sets the printarea for the sheet provided
*
* 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.
*/
public void setRepeatingRowsAndColumns(int sheetIndex,
int startColumn, int endColumn,
int startRow, int endRow) {
// Check arguments
if ((startColumn == -1 && endColumn != -1) || startColumn < -1 || endColumn < -1 || startColumn > endColumn)
throw new IllegalArgumentException("Invalid column range specification");
if ((startRow == -1 && endRow != -1) || startRow < -1 || endRow < -1 || startRow > endRow)
throw new IllegalArgumentException("Invalid row range specification");
XSSFSheet sheet = getSheetAt(sheetIndex);
boolean removingRange = startColumn == -1 && endColumn == -1 && startRow == -1 && endRow == -1;
XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex);
if (removingRange && name != null) {
namedRanges.remove(name);
return;
}
if (name == null) {
name = createBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex);
String reference = getReferenceBuiltInRecord(name.getSheetName(), startColumn, endColumn, startRow, endRow);
name.setRefersToFormula(reference);
namedRanges.add(name);
}
XSSFPrintSetup printSetup = sheet.getPrintSetup();
printSetup.setValidSettings(false);
}
private static String getReferenceBuiltInRecord(String sheetName, int startC, int endC, int startR, int endR) {
//windows excel example for built-in title: 'second sheet'!$E:$F,'second sheet'!$2:$3
CellReference colRef = new CellReference(sheetName, 0, startC, true, true);
CellReference colRef2 = new CellReference(sheetName, 0, endC, true, true);
String c = "'" + sheetName + "'!$" + colRef.getCellRefParts()[2] + ":$" + colRef2.getCellRefParts()[2];
CellReference rowRef = new CellReference(sheetName, startR, 0, true, true);
CellReference rowRef2 = new CellReference(sheetName, endR, 0, true, true);
String r = "";
if (!rowRef.getCellRefParts()[1].equals("0") && !rowRef2.getCellRefParts()[1].equals("0")) {
r = ",'" + sheetName + "'!$" + rowRef.getCellRefParts()[1] + ":$" + rowRef2.getCellRefParts()[1];
}
return c + r;
}
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];
}
private 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
*/
private 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 + ")");
}
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.
* Will throw IllegalArgumentException if the name is greater than 31 chars
* or contains /\?*[]
*
* @param sheet number (0 based)
* @see #validateSheetName(String)
*/
public void setSheetName(int sheet, String name) {
validateSheetIndex(sheet);
validateSheetName(name);
if (containsSheet(name, sheet ))
throw new IllegalArgumentException( "The workbook already contains a sheet of this name" );
workbook.getSheets().getSheetArray(sheet).setName(name);
}
/**
* 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().getCArray().length;
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 string MUST NOT begin or end with the single quote (') character.
*
* 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
*/
public CalculationChain getCalculationChain(){
return calcChain;
}
}