123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /* ====================================================================
- 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.util.HashMap;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
-
- import org.apache.poi.ss.SpreadsheetVersion;
- import org.apache.poi.ss.formula.EvaluationName;
- import org.apache.poi.ss.formula.EvaluationWorkbook;
- import org.apache.poi.ss.formula.FormulaParser;
- import org.apache.poi.ss.formula.FormulaParsingWorkbook;
- import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
- import org.apache.poi.ss.formula.FormulaType;
- import org.apache.poi.ss.formula.SheetIdentifier;
- import org.apache.poi.ss.formula.functions.FreeRefFunction;
- import org.apache.poi.ss.formula.ptg.Area3DPxg;
- import org.apache.poi.ss.formula.ptg.NamePtg;
- import org.apache.poi.ss.formula.ptg.NameXPtg;
- import org.apache.poi.ss.formula.ptg.NameXPxg;
- import org.apache.poi.ss.formula.ptg.Ptg;
- import org.apache.poi.ss.formula.ptg.Ref3DPxg;
- import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
- import org.apache.poi.ss.formula.udf.UDFFinder;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.util.AreaReference;
- import org.apache.poi.ss.util.CellReference;
- import org.apache.poi.util.NotImplemented;
- import org.apache.poi.util.Internal;
- import org.apache.poi.xssf.model.ExternalLinksTable;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
-
- /**
- * Internal POI use only - parent of XSSF and SXSSF evaluation workbooks
- */
- @Internal
- public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
- protected final XSSFWorkbook _uBook;
-
- // lazily populated. This should only be accessed through getTableCache
- // keys are lower-case to make this a quasi-case-insensitive map
- private Map<String, XSSFTable> _tableCache;
-
-
- protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) {
- _uBook = book;
- }
-
- /* (non-JavaDoc), inherit JavaDoc from EvaluationWorkbook
- * @since POI 3.15 beta 3
- */
- @Override
- public void clearAllCachedResultValues() {
- _tableCache = null;
- }
-
- private int convertFromExternalSheetIndex(int externSheetIndex) {
- return externSheetIndex;
- }
- /**
- * XSSF doesn't use external sheet indexes, so when asked treat
- * it just as a local index
- */
- @Override
- public int convertFromExternSheetIndex(int externSheetIndex) {
- return externSheetIndex;
- }
- /**
- * @return the external sheet index of the sheet with the given internal
- * index. Used by some of the more obscure formula and named range things.
- * Fairly easy on XSSF (we think...) since the internal and external
- * indices are the same
- */
- private int convertToExternalSheetIndex(int sheetIndex) {
- return sheetIndex;
- }
-
- @Override
- public int getExternalSheetIndex(String sheetName) {
- int sheetIndex = _uBook.getSheetIndex(sheetName);
- return convertToExternalSheetIndex(sheetIndex);
- }
-
- private int resolveBookIndex(String bookName) {
- // Strip the [] wrapper, if still present
- if (bookName.startsWith("[") && bookName.endsWith("]")) {
- bookName = bookName.substring(1, bookName.length()-2);
- }
-
- // Is it already in numeric form?
- try {
- return Integer.parseInt(bookName);
- } catch (NumberFormatException e) {}
-
- // Look up an External Link Table for this name
- List<ExternalLinksTable> tables = _uBook.getExternalLinksTable();
- int index = findExternalLinkIndex(bookName, tables);
- if (index != -1) return index;
-
- // Is it an absolute file reference?
- if (bookName.startsWith("'file:///") && bookName.endsWith("'")) {
- String relBookName = bookName.substring(bookName.lastIndexOf('/')+1);
- relBookName = relBookName.substring(0, relBookName.length()-1); // Trailing '
-
- // Try with this name
- index = findExternalLinkIndex(relBookName, tables);
- if (index != -1) return index;
-
- // If we get here, it's got no associated proper links yet
- // So, add the missing reference and return
- // Note - this is really rather nasty...
- ExternalLinksTable fakeLinkTable = new FakeExternalLinksTable(relBookName);
- tables.add(fakeLinkTable);
- return tables.size(); // 1 based results, 0 = current workbook
- }
-
- // Not properly referenced
- throw new RuntimeException("Book not linked for filename " + bookName);
- }
- /* This is case-sensitive. Is that correct? */
- private int findExternalLinkIndex(String bookName, List<ExternalLinksTable> tables) {
- int i = 0;
- for (ExternalLinksTable table : tables) {
- if (table.getLinkedFileName().equals(bookName)) {
- return i+1; // 1 based results, 0 = current workbook
- }
- i++;
- }
- return -1;
- }
- private static class FakeExternalLinksTable extends ExternalLinksTable {
- private final String fileName;
- private FakeExternalLinksTable(String fileName) {
- this.fileName = fileName;
- }
- @Override
- public String getLinkedFileName() {
- return fileName;
- }
- }
-
- /**
- * Return EvaluationName wrapper around the matching XSSFName (named range)
- * @param name case-aware but case-insensitive named range in workbook
- * @param sheetIndex index of sheet if named range scope is limited to one sheet
- * if named range scope is global to the workbook, sheetIndex is -1.
- * @return If name is a named range in the workbook, returns
- * EvaluationName corresponding to that named range
- * Returns null if there is no named range with the same name and scope in the workbook
- */
- @Override
- public EvaluationName getName(String name, int sheetIndex) {
- for (int i = 0; i < _uBook.getNumberOfNames(); i++) {
- XSSFName nm = _uBook.getNameAt(i);
- String nameText = nm.getNameName();
- int nameSheetindex = nm.getSheetIndex();
- if (name.equalsIgnoreCase(nameText) &&
- (nameSheetindex == -1 || nameSheetindex == sheetIndex)) {
- return new Name(nm, i, this);
- }
- }
- return sheetIndex == -1 ? null : getName(name, -1);
- }
-
- @Override
- public String getSheetName(int sheetIndex) {
- return _uBook.getSheetName(sheetIndex);
- }
-
- @Override
- public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
- throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
- }
-
- @Override
- public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) {
- if (externalWorkbookNumber > 0) {
- // External reference - reference is 1 based, link table is 0 based
- int linkNumber = externalWorkbookNumber - 1;
- ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
-
- for (org.apache.poi.ss.usermodel.Name name : linkTable.getDefinedNames()) {
- if (name.getNameName().equals(nameName)) {
- // HSSF returns one sheet higher than normal, and various bits
- // of the code assume that. So, make us match that behaviour!
- int nameSheetIndex = name.getSheetIndex() + 1;
-
- // TODO Return a more specialised form of this, see bug #56752
- // Should include the cached values, for in case that book isn't available
- // Should support XSSF stuff lookups
- return new ExternalName(nameName, -1, nameSheetIndex);
- }
- }
- throw new IllegalArgumentException("Name '"+nameName+"' not found in " +
- "reference to " + linkTable.getLinkedFileName());
- } else {
- // Internal reference
- int nameIdx = _uBook.getNameIndex(nameName);
- return new ExternalName(nameName, nameIdx, 0); // TODO Is this right?
- }
-
- }
-
- /**
- * Return an external name (named range, function, user-defined function) Pxg
- */
- @Override
- public NameXPxg getNameXPtg(String name, SheetIdentifier sheet) {
- // First, try to find it as a User Defined Function
- IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
- FreeRefFunction func = udfFinder.findFunction(name);
- if (func != null) {
- return new NameXPxg(null, name);
- }
-
- // Otherwise, try it as a named range
- if (sheet == null) {
- if (!_uBook.getNames(name).isEmpty()) {
- return new NameXPxg(null, name);
- }
- return null;
- }
- if (sheet._sheetIdentifier == null) {
- // Workbook + Named Range only
- int bookIndex = resolveBookIndex(sheet._bookName);
- return new NameXPxg(bookIndex, null, name);
- }
-
- // Use the sheetname and process
- String sheetName = sheet._sheetIdentifier.getName();
-
- if (sheet._bookName != null) {
- int bookIndex = resolveBookIndex(sheet._bookName);
- return new NameXPxg(bookIndex, sheetName, name);
- } else {
- return new NameXPxg(sheetName, name);
- }
- }
- @Override
- public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) {
- if (sheet._bookName != null) {
- int bookIndex = resolveBookIndex(sheet._bookName);
- return new Ref3DPxg(bookIndex, sheet, cell);
- } else {
- return new Ref3DPxg(sheet, cell);
- }
- }
- @Override
- public Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet) {
- if (sheet._bookName != null) {
- int bookIndex = resolveBookIndex(sheet._bookName);
- return new Area3DPxg(bookIndex, sheet, area);
- } else {
- return new Area3DPxg(sheet, area);
- }
- }
-
- @Override
- public String resolveNameXText(NameXPtg n) {
- int idx = n.getNameIndex();
- String name = null;
-
- // First, try to find it as a User Defined Function
- IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
- name = udfFinder.getFunctionName(idx);
- if (name != null) return name;
-
- // Otherwise, try it as a named range
- XSSFName xname = _uBook.getNameAt(idx);
- if (xname != null) {
- name = xname.getNameName();
- }
-
- return name;
- }
-
- @Override
- public ExternalSheet getExternalSheet(int externSheetIndex) {
- throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
- }
- @Override
- public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
- String workbookName;
- if (externalWorkbookNumber > 0) {
- // External reference - reference is 1 based, link table is 0 based
- int linkNumber = externalWorkbookNumber - 1;
- ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
- workbookName = linkTable.getLinkedFileName();
- } else {
- // Internal reference
- workbookName = null;
- }
-
- if (lastSheetName == null || firstSheetName.equals(lastSheetName)) {
- return new ExternalSheet(workbookName, firstSheetName);
- } else {
- return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName);
- }
- }
-
- @NotImplemented
- public int getExternalSheetIndex(String workbookName, String sheetName) {
- throw new RuntimeException("not implemented yet");
- }
- @Override
- public int getSheetIndex(String sheetName) {
- return _uBook.getSheetIndex(sheetName);
- }
-
- @Override
- public String getSheetFirstNameByExternSheet(int externSheetIndex) {
- int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
- return _uBook.getSheetName(sheetIndex);
- }
- @Override
- public String getSheetLastNameByExternSheet(int externSheetIndex) {
- // XSSF does multi-sheet references differently, so this is the same as the first
- return getSheetFirstNameByExternSheet(externSheetIndex);
- }
-
- @Override
- public String getNameText(NamePtg namePtg) {
- return _uBook.getNameAt(namePtg.getIndex()).getNameName();
- }
- @Override
- public EvaluationName getName(NamePtg namePtg) {
- int ix = namePtg.getIndex();
- return new Name(_uBook.getNameAt(ix), ix, this);
- }
- @Override
- public XSSFName createName() {
- return _uBook.createName();
- }
-
- private static String caseInsensitive(String s) {
- return s.toUpperCase(Locale.ROOT);
- }
-
- /*
- * TODO: data tables are stored at the workbook level in XSSF, but are bound to a single sheet.
- * The current code structure has them hanging off XSSFSheet, but formulas reference them
- * only by name (names are global, and case insensitive).
- * This map stores names as lower case for case-insensitive lookups.
- *
- * FIXME: Caching tables by name here for fast formula lookup means the map is out of date if
- * a table is renamed or added/removed to a sheet after the map is created.
- *
- * Perhaps tables can be managed similar to PivotTable references above?
- */
- private Map<String, XSSFTable> getTableCache() {
- if ( _tableCache != null ) {
- return _tableCache;
- }
- // FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap
- _tableCache = new HashMap<>();
-
- for (Sheet sheet : _uBook) {
- for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
- String lname = caseInsensitive(tbl.getName());
- _tableCache.put(lname, tbl);
- }
- }
- return _tableCache;
- }
-
- /**
- * Returns the data table with the given name (case insensitive).
- * Tables are cached for performance (formula evaluation looks them up by name repeatedly).
- * After the first table lookup, adding or removing a table from the document structure will cause trouble.
- * This is meant to be used on documents whose structure is essentially static at the point formulas are evaluated.
- *
- * @param name the data table name (case-insensitive)
- * @return The Data table in the workbook named <tt>name</tt>, or <tt>null</tt> if no table is named <tt>name</tt>.
- * @since 3.15 beta 2
- */
- @Override
- public XSSFTable getTable(String name) {
- if (name == null) return null;
- String lname = caseInsensitive(name);
- return getTableCache().get(lname);
- }
-
- @Override
- public UDFFinder getUDFFinder(){
- return _uBook.getUDFFinder();
- }
-
- @Override
- public SpreadsheetVersion getSpreadsheetVersion(){
- return SpreadsheetVersion.EXCEL2007;
- }
-
- private static final class Name implements EvaluationName {
-
- private final XSSFName _nameRecord;
- private final int _index;
- private final FormulaParsingWorkbook _fpBook;
-
- public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {
- _nameRecord = name;
- _index = index;
- _fpBook = fpBook;
- }
-
- public Ptg[] getNameDefinition() {
-
- return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex());
- }
-
- public String getNameText() {
- return _nameRecord.getNameName();
- }
-
- public boolean hasFormula() {
- // TODO - no idea if this is right
- CTDefinedName ctn = _nameRecord.getCTName();
- String strVal = ctn.getStringValue();
- return !ctn.getFunction() && strVal != null && strVal.length() > 0;
- }
-
- public boolean isFunctionName() {
- return _nameRecord.isFunctionName();
- }
-
- public boolean isRange() {
- return hasFormula(); // TODO - is this right?
- }
- public NamePtg createPtg() {
- return new NamePtg(_index);
- }
- }
- }
|