Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

BaseXSSFEvaluationWorkbook.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.usermodel;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.concurrent.ConcurrentSkipListMap;
  19. import org.apache.poi.ss.SpreadsheetVersion;
  20. import org.apache.poi.ss.formula.EvaluationName;
  21. import org.apache.poi.ss.formula.EvaluationWorkbook;
  22. import org.apache.poi.ss.formula.FormulaParser;
  23. import org.apache.poi.ss.formula.FormulaParsingWorkbook;
  24. import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
  25. import org.apache.poi.ss.formula.FormulaType;
  26. import org.apache.poi.ss.formula.SheetIdentifier;
  27. import org.apache.poi.ss.formula.functions.FreeRefFunction;
  28. import org.apache.poi.ss.formula.ptg.Area3DPxg;
  29. import org.apache.poi.ss.formula.ptg.NamePtg;
  30. import org.apache.poi.ss.formula.ptg.NameXPtg;
  31. import org.apache.poi.ss.formula.ptg.NameXPxg;
  32. import org.apache.poi.ss.formula.ptg.Ptg;
  33. import org.apache.poi.ss.formula.ptg.Ref3DPxg;
  34. import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
  35. import org.apache.poi.ss.formula.udf.UDFFinder;
  36. import org.apache.poi.ss.usermodel.Sheet;
  37. import org.apache.poi.ss.util.AreaReference;
  38. import org.apache.poi.ss.util.CellReference;
  39. import org.apache.poi.util.NotImplemented;
  40. import org.apache.poi.util.Internal;
  41. import org.apache.poi.xssf.model.ExternalLinksTable;
  42. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
  43. /**
  44. * Internal POI use only - parent of XSSF and SXSSF evaluation workbooks
  45. */
  46. @Internal
  47. public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
  48. protected final XSSFWorkbook _uBook;
  49. // lazily populated. This should only be accessed through getTableCache
  50. // keys are lower-case to make this a quasi-case-insensitive map
  51. private Map<String, XSSFTable> _tableCache;
  52. protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) {
  53. _uBook = book;
  54. }
  55. /* (non-JavaDoc), inherit JavaDoc from EvaluationWorkbook
  56. * @since POI 3.15 beta 3
  57. */
  58. @Override
  59. public void clearAllCachedResultValues() {
  60. _tableCache = null;
  61. }
  62. private int convertFromExternalSheetIndex(int externSheetIndex) {
  63. return externSheetIndex;
  64. }
  65. /**
  66. * XSSF doesn't use external sheet indexes, so when asked treat
  67. * it just as a local index
  68. */
  69. @Override
  70. public int convertFromExternSheetIndex(int externSheetIndex) {
  71. return externSheetIndex;
  72. }
  73. /**
  74. * @return the external sheet index of the sheet with the given internal
  75. * index. Used by some of the more obscure formula and named range things.
  76. * Fairly easy on XSSF (we think...) since the internal and external
  77. * indices are the same
  78. */
  79. private int convertToExternalSheetIndex(int sheetIndex) {
  80. return sheetIndex;
  81. }
  82. @Override
  83. public int getExternalSheetIndex(String sheetName) {
  84. int sheetIndex = _uBook.getSheetIndex(sheetName);
  85. return convertToExternalSheetIndex(sheetIndex);
  86. }
  87. private int resolveBookIndex(String bookName) {
  88. // Strip the [] wrapper, if still present
  89. if (bookName.startsWith("[") && bookName.endsWith("]")) {
  90. bookName = bookName.substring(1, bookName.length()-2);
  91. }
  92. // Is it already in numeric form?
  93. try {
  94. return Integer.parseInt(bookName);
  95. } catch (NumberFormatException e) {}
  96. // Look up an External Link Table for this name
  97. List<ExternalLinksTable> tables = _uBook.getExternalLinksTable();
  98. int index = findExternalLinkIndex(bookName, tables);
  99. if (index != -1) return index;
  100. // Is it an absolute file reference?
  101. if (bookName.startsWith("'file:///") && bookName.endsWith("'")) {
  102. String relBookName = bookName.substring(bookName.lastIndexOf('/')+1);
  103. relBookName = relBookName.substring(0, relBookName.length()-1); // Trailing '
  104. // Try with this name
  105. index = findExternalLinkIndex(relBookName, tables);
  106. if (index != -1) return index;
  107. // If we get here, it's got no associated proper links yet
  108. // So, add the missing reference and return
  109. // Note - this is really rather nasty...
  110. ExternalLinksTable fakeLinkTable = new FakeExternalLinksTable(relBookName);
  111. tables.add(fakeLinkTable);
  112. return tables.size(); // 1 based results, 0 = current workbook
  113. }
  114. // Not properly referenced
  115. throw new IllegalStateException("Book not linked for filename " + bookName);
  116. }
  117. /* This is case-sensitive. Is that correct? */
  118. private int findExternalLinkIndex(String bookName, List<ExternalLinksTable> tables) {
  119. int i = 0;
  120. for (ExternalLinksTable table : tables) {
  121. if (table.getLinkedFileName().equals(bookName)) {
  122. return i+1; // 1 based results, 0 = current workbook
  123. }
  124. i++;
  125. }
  126. return -1;
  127. }
  128. private static class FakeExternalLinksTable extends ExternalLinksTable {
  129. private final String fileName;
  130. private FakeExternalLinksTable(String fileName) {
  131. this.fileName = fileName;
  132. }
  133. @Override
  134. public String getLinkedFileName() {
  135. return fileName;
  136. }
  137. }
  138. /**
  139. * Return EvaluationName wrapper around the matching XSSFName (named range)
  140. * @param name case-aware but case-insensitive named range in workbook
  141. * @param sheetIndex index of sheet if named range scope is limited to one sheet
  142. * if named range scope is global to the workbook, sheetIndex is -1.
  143. * @return If name is a named range in the workbook, returns
  144. * EvaluationName corresponding to that named range
  145. * Returns null if there is no named range with the same name and scope in the workbook
  146. */
  147. @Override
  148. public EvaluationName getName(String name, int sheetIndex) {
  149. for (int i = 0; i < _uBook.getNumberOfNames(); i++) {
  150. XSSFName nm = _uBook.getNameAt(i);
  151. String nameText = nm.getNameName();
  152. int nameSheetindex = nm.getSheetIndex();
  153. if (name.equalsIgnoreCase(nameText) &&
  154. (nameSheetindex == -1 || nameSheetindex == sheetIndex)) {
  155. return new Name(nm, i, this);
  156. }
  157. }
  158. return sheetIndex == -1 ? null : getName(name, -1);
  159. }
  160. @Override
  161. public String getSheetName(int sheetIndex) {
  162. return _uBook.getSheetName(sheetIndex);
  163. }
  164. @Override
  165. public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
  166. throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
  167. }
  168. @Override
  169. public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) {
  170. if (externalWorkbookNumber > 0) {
  171. // External reference - reference is 1 based, link table is 0 based
  172. int linkNumber = externalWorkbookNumber - 1;
  173. ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
  174. for (org.apache.poi.ss.usermodel.Name name : linkTable.getDefinedNames()) {
  175. if (name.getNameName().equals(nameName)) {
  176. // HSSF returns one sheet higher than normal, and various bits
  177. // of the code assume that. So, make us match that behaviour!
  178. int nameSheetIndex = name.getSheetIndex() + 1;
  179. // TODO Return a more specialised form of this, see bug #56752
  180. // Should include the cached values, for in case that book isn't available
  181. // Should support XSSF stuff lookups
  182. return new ExternalName(nameName, -1, nameSheetIndex);
  183. }
  184. }
  185. throw new IllegalArgumentException("Name '"+nameName+"' not found in " +
  186. "reference to " + linkTable.getLinkedFileName());
  187. } else {
  188. // Internal reference
  189. int nameIdx = _uBook.getNameIndex(nameName);
  190. return new ExternalName(nameName, nameIdx, 0); // TODO Is this right?
  191. }
  192. }
  193. /**
  194. * Return an external name (named range, function, user-defined function) Pxg
  195. */
  196. @Override
  197. public NameXPxg getNameXPtg(String name, SheetIdentifier sheet) {
  198. // First, try to find it as a User Defined Function
  199. IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
  200. FreeRefFunction func = udfFinder.findFunction(name);
  201. if (func != null) {
  202. return new NameXPxg(null, name);
  203. }
  204. // Otherwise, try it as a named range
  205. if (sheet == null) {
  206. if (!_uBook.getNames(name).isEmpty()) {
  207. return new NameXPxg(null, name);
  208. }
  209. return null;
  210. }
  211. if (sheet.getSheetIdentifier() == null) {
  212. // Workbook + Named Range only
  213. int bookIndex = resolveBookIndex(sheet.getBookName());
  214. return new NameXPxg(bookIndex, null, name);
  215. }
  216. // Use the sheetname and process
  217. String sheetName = sheet.getSheetIdentifier().getName();
  218. if (sheet.getBookName() != null) {
  219. int bookIndex = resolveBookIndex(sheet.getBookName());
  220. return new NameXPxg(bookIndex, sheetName, name);
  221. } else {
  222. return new NameXPxg(sheetName, name);
  223. }
  224. }
  225. @Override
  226. public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) {
  227. if (sheet.getBookName() != null) {
  228. int bookIndex = resolveBookIndex(sheet.getBookName());
  229. return new Ref3DPxg(bookIndex, sheet, cell);
  230. } else {
  231. return new Ref3DPxg(sheet, cell);
  232. }
  233. }
  234. @Override
  235. public Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet) {
  236. if (sheet.getBookName() != null) {
  237. int bookIndex = resolveBookIndex(sheet.getBookName());
  238. return new Area3DPxg(bookIndex, sheet, area);
  239. } else {
  240. return new Area3DPxg(sheet, area);
  241. }
  242. }
  243. @Override
  244. public String resolveNameXText(NameXPtg n) {
  245. int idx = n.getNameIndex();
  246. String name = null;
  247. // First, try to find it as a User Defined Function
  248. IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
  249. name = udfFinder.getFunctionName(idx);
  250. if (name != null) return name;
  251. // Otherwise, try it as a named range
  252. XSSFName xname = _uBook.getNameAt(idx);
  253. if (xname != null) {
  254. name = xname.getNameName();
  255. }
  256. return name;
  257. }
  258. @Override
  259. public ExternalSheet getExternalSheet(int externSheetIndex) {
  260. throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
  261. }
  262. @Override
  263. public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
  264. String workbookName;
  265. if (externalWorkbookNumber > 0) {
  266. // External reference - reference is 1 based, link table is 0 based
  267. int linkNumber = externalWorkbookNumber - 1;
  268. ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
  269. workbookName = linkTable.getLinkedFileName();
  270. } else {
  271. // Internal reference
  272. workbookName = null;
  273. }
  274. if (lastSheetName == null || firstSheetName.equals(lastSheetName)) {
  275. return new ExternalSheet(workbookName, firstSheetName);
  276. } else {
  277. return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName);
  278. }
  279. }
  280. @Override
  281. @NotImplemented
  282. public int getExternalSheetIndex(String workbookName, String sheetName) {
  283. throw new IllegalStateException("not implemented yet");
  284. }
  285. @Override
  286. public int getSheetIndex(String sheetName) {
  287. return _uBook.getSheetIndex(sheetName);
  288. }
  289. @Override
  290. public String getSheetFirstNameByExternSheet(int externSheetIndex) {
  291. int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
  292. return _uBook.getSheetName(sheetIndex);
  293. }
  294. @Override
  295. public String getSheetLastNameByExternSheet(int externSheetIndex) {
  296. // XSSF does multi-sheet references differently, so this is the same as the first
  297. return getSheetFirstNameByExternSheet(externSheetIndex);
  298. }
  299. @Override
  300. public String getNameText(NamePtg namePtg) {
  301. return _uBook.getNameAt(namePtg.getIndex()).getNameName();
  302. }
  303. @Override
  304. public EvaluationName getName(NamePtg namePtg) {
  305. int ix = namePtg.getIndex();
  306. return new Name(_uBook.getNameAt(ix), ix, this);
  307. }
  308. @Override
  309. public XSSFName createName() {
  310. return _uBook.createName();
  311. }
  312. /*
  313. * TODO: data tables are stored at the workbook level in XSSF, but are bound to a single sheet.
  314. * The current code structure has them hanging off XSSFSheet, but formulas reference them
  315. * only by name (names are global, and case insensitive).
  316. * This map stores names as lower case for case-insensitive lookups.
  317. *
  318. * FIXME: Caching tables by name here for fast formula lookup means the map is out of date if
  319. * a table is renamed or added/removed to a sheet after the map is created.
  320. *
  321. * Perhaps tables can be managed similar to PivotTable references above?
  322. */
  323. private Map<String, XSSFTable> getTableCache() {
  324. if ( _tableCache != null ) {
  325. return _tableCache;
  326. }
  327. _tableCache = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
  328. for (Sheet sheet : _uBook) {
  329. for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
  330. _tableCache.put(tbl.getName(), tbl);
  331. }
  332. }
  333. return _tableCache;
  334. }
  335. /**
  336. * Returns the data table with the given name (case insensitive).
  337. * Tables are cached for performance (formula evaluation looks them up by name repeatedly).
  338. * After the first table lookup, adding or removing a table from the document structure will cause trouble.
  339. * This is meant to be used on documents whose structure is essentially static at the point formulas are evaluated.
  340. *
  341. * @param name the data table name (case-insensitive)
  342. * @return The Data table in the workbook named {@code name}, or {@code null} if no table is named {@code name}.
  343. * @since 3.15 beta 2
  344. */
  345. @Override
  346. public XSSFTable getTable(String name) {
  347. if (name == null) return null;
  348. return getTableCache().get(name);
  349. }
  350. @Override
  351. public UDFFinder getUDFFinder(){
  352. return _uBook.getUDFFinder();
  353. }
  354. @Override
  355. public SpreadsheetVersion getSpreadsheetVersion(){
  356. return SpreadsheetVersion.EXCEL2007;
  357. }
  358. private static final class Name implements EvaluationName {
  359. private final XSSFName _nameRecord;
  360. private final int _index;
  361. private final FormulaParsingWorkbook _fpBook;
  362. public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {
  363. _nameRecord = name;
  364. _index = index;
  365. _fpBook = fpBook;
  366. }
  367. @Override
  368. public Ptg[] getNameDefinition() {
  369. return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex());
  370. }
  371. @Override
  372. public String getNameText() {
  373. return _nameRecord.getNameName();
  374. }
  375. @Override
  376. public boolean hasFormula() {
  377. // TODO - no idea if this is right
  378. CTDefinedName ctn = _nameRecord.getCTName();
  379. String strVal = ctn.getStringValue();
  380. return !ctn.getFunction() && strVal != null && strVal.length() > 0;
  381. }
  382. @Override
  383. public boolean isFunctionName() {
  384. return _nameRecord.isFunctionName();
  385. }
  386. @Override
  387. public boolean isRange() {
  388. return hasFormula(); // TODO - is this right?
  389. }
  390. @Override
  391. public NamePtg createPtg() {
  392. return new NamePtg(_index);
  393. }
  394. }
  395. }