You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSSFBReader.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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.eventusermodel;
  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.Iterator;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Set;
  26. import com.zaxxer.sparsebits.SparseBitSet;
  27. import org.apache.logging.log4j.LogManager;
  28. import org.apache.logging.log4j.Logger;
  29. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  30. import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
  31. import org.apache.poi.openxml4j.opc.OPCPackage;
  32. import org.apache.poi.openxml4j.opc.PackagePart;
  33. import org.apache.poi.openxml4j.opc.PackagePartName;
  34. import org.apache.poi.openxml4j.opc.PackageRelationship;
  35. import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
  36. import org.apache.poi.openxml4j.opc.PackagingURIHelper;
  37. import org.apache.poi.util.LittleEndian;
  38. import org.apache.poi.util.LittleEndianConsts;
  39. import org.apache.poi.util.StringUtil;
  40. import org.apache.poi.xssf.binary.XSSFBCommentsTable;
  41. import org.apache.poi.xssf.binary.XSSFBParseException;
  42. import org.apache.poi.xssf.binary.XSSFBParser;
  43. import org.apache.poi.xssf.binary.XSSFBRecordType;
  44. import org.apache.poi.xssf.binary.XSSFBRelation;
  45. import org.apache.poi.xssf.binary.XSSFBStylesTable;
  46. import org.apache.poi.xssf.binary.XSSFBUtils;
  47. import org.apache.poi.xssf.model.CommentsTable;
  48. import org.apache.poi.xssf.usermodel.XSSFRelation;
  49. /**
  50. * Reader for xlsb files.
  51. *
  52. * @since 3.16-beta3
  53. */
  54. public class XSSFBReader extends XSSFReader {
  55. private static final Logger LOGGER = LogManager.getLogger(XSSFBReader.class);
  56. private static final Set<String> WORKSHEET_RELS =
  57. Collections.unmodifiableSet(new HashSet<>(
  58. Arrays.asList(
  59. XSSFRelation.WORKSHEET.getRelation(),
  60. XSSFRelation.CHARTSHEET.getRelation(),
  61. XSSFRelation.MACRO_SHEET_BIN.getRelation(),
  62. XSSFRelation.INTL_MACRO_SHEET_BIN.getRelation(),
  63. XSSFRelation.DIALOG_SHEET_BIN.getRelation()
  64. )
  65. ));
  66. /**
  67. * Creates a new XSSFReader, for the given package
  68. *
  69. * @param pkg opc package
  70. * @throws OpenXML4JException if the package data format is invalid
  71. * @throws IOException if there is an I/O issue reading the data
  72. */
  73. public XSSFBReader(OPCPackage pkg) throws IOException, OpenXML4JException {
  74. super(pkg);
  75. }
  76. /**
  77. * In Excel 2013, the absolute path where the file was last saved may be stored in
  78. * the {@link XSSFBRecordType#BrtAbsPath15} record. The equivalent in ooxml is
  79. * &lt;x15ac:absPath&gt;.
  80. *
  81. * @return absolute path or <code>null</code> if it could not be found.
  82. * @throws IOException when there's a problem with the workbook part's stream
  83. */
  84. public String getAbsPathMetadata() throws IOException {
  85. try (InputStream is = workbookPart.getInputStream()) {
  86. PathExtractor p = new PathExtractor(is);
  87. p.parse();
  88. return p.getPath();
  89. }
  90. }
  91. /**
  92. * Returns an Iterator which will let you get at all the
  93. * different Sheets in turn.
  94. * Each sheet's InputStream is only opened when fetched
  95. * from the Iterator. It's up to you to close the
  96. * InputStreams when done with each one.
  97. *
  98. * @return iterator of {@link InputStream}s
  99. * @throws InvalidFormatException if the sheet data format is invalid
  100. * @throws IOException if there is an I/O issue reading the data
  101. */
  102. @Override
  103. public Iterator<InputStream> getSheetsData() throws IOException, InvalidFormatException {
  104. return new SheetIterator(workbookPart);
  105. }
  106. public XSSFBStylesTable getXSSFBStylesTable() throws IOException {
  107. ArrayList<PackagePart> parts = pkg.getPartsByContentType(XSSFBRelation.STYLES_BINARY.getContentType());
  108. if(parts.isEmpty()) return null;
  109. // Create the Styles Table, and associate the Themes if present
  110. try (InputStream stream = parts.get(0).getInputStream()) {
  111. return new XSSFBStylesTable(stream);
  112. }
  113. }
  114. public static class SheetIterator extends XSSFReader.SheetIterator {
  115. /**
  116. * Construct a new SheetIterator
  117. *
  118. * @param wb package part holding workbook.xml
  119. * @throws InvalidFormatException if the sheet data format is invalid
  120. * @throws IOException if there is an I/O issue reading the data
  121. */
  122. private SheetIterator(PackagePart wb) throws IOException, InvalidFormatException {
  123. super(wb);
  124. }
  125. @Override
  126. protected Set<String> getSheetRelationships() {
  127. return WORKSHEET_RELS;
  128. }
  129. @Override
  130. protected Iterator<XSSFSheetRef> createSheetIteratorFromWB(PackagePart wb) throws IOException {
  131. try (InputStream stream = wb.getInputStream()) {
  132. SheetRefLoader sheetRefLoader = new SheetRefLoader(stream);
  133. sheetRefLoader.parse();
  134. return sheetRefLoader.getSheets().iterator();
  135. }
  136. }
  137. /**
  138. * Not supported by XSSFBReader's SheetIterator.
  139. * Please use {@link #getXSSFBSheetComments()} instead.
  140. * @return nothing, always throws IllegalArgumentException!
  141. */
  142. @Override
  143. public CommentsTable getSheetComments() {
  144. throw new IllegalArgumentException("Please use getXSSFBSheetComments");
  145. }
  146. public XSSFBCommentsTable getXSSFBSheetComments() {
  147. PackagePart sheetPkg = getSheetPart();
  148. // Do we have a comments relationship? (Only ever one if so)
  149. try {
  150. PackageRelationshipCollection commentsList =
  151. sheetPkg.getRelationshipsByType(XSSFRelation.SHEET_COMMENTS.getRelation());
  152. if (!commentsList.isEmpty()) {
  153. PackageRelationship comments = commentsList.getRelationship(0);
  154. if (comments == null || comments.getTargetURI() == null) {
  155. return null;
  156. }
  157. PackagePartName commentsName = PackagingURIHelper.createPartName(comments.getTargetURI());
  158. PackagePart commentsPart = sheetPkg.getPackage().getPart(commentsName);
  159. try (InputStream stream = commentsPart.getInputStream()) {
  160. return new XSSFBCommentsTable(stream);
  161. }
  162. }
  163. } catch (InvalidFormatException | IOException e) {
  164. return null;
  165. }
  166. return null;
  167. }
  168. }
  169. private static class PathExtractor extends XSSFBParser {
  170. private static final SparseBitSet RECORDS = new SparseBitSet();
  171. static {
  172. RECORDS.set(XSSFBRecordType.BrtAbsPath15.getId());
  173. }
  174. private String path;
  175. public PathExtractor(InputStream is) {
  176. super(is, RECORDS);
  177. }
  178. @Override
  179. public void handleRecord(int recordType, byte[] data) throws XSSFBParseException {
  180. if (recordType != XSSFBRecordType.BrtAbsPath15.getId()) {
  181. return;
  182. }
  183. StringBuilder sb = new StringBuilder();
  184. XSSFBUtils.readXLWideString(data, 0, sb);
  185. path = sb.toString();
  186. }
  187. /**
  188. *
  189. * @return the path if found, otherwise <code>null</code>
  190. */
  191. String getPath() {
  192. return path;
  193. }
  194. }
  195. private static class SheetRefLoader extends XSSFBParser {
  196. List<XSSFSheetRef> sheets = new LinkedList<>();
  197. private SheetRefLoader(InputStream is) {
  198. super(is);
  199. }
  200. @Override
  201. public void handleRecord(int recordType, byte[] data) throws XSSFBParseException {
  202. if (recordType == XSSFBRecordType.BrtBundleSh.getId()) {
  203. addWorksheet(data);
  204. }
  205. }
  206. private void addWorksheet(byte[] data) {
  207. //try to parse the BrtBundleSh
  208. //if there's an exception, catch it and
  209. //try to figure out if this is one of the old beta-created xlsb files
  210. //or if this is a general exception
  211. try {
  212. tryToAddWorksheet(data);
  213. } catch (XSSFBParseException e) {
  214. if (tryOldFormat(data)) {
  215. LOGGER.atWarn().log("This file was written with a beta version of Excel. "+
  216. "POI will try to parse the file as a regular xlsb.");
  217. } else {
  218. throw e;
  219. }
  220. }
  221. }
  222. private void tryToAddWorksheet(byte[] data) throws XSSFBParseException {
  223. int offset = 0;
  224. //this is the sheet state #2.5.142
  225. /*long hsShtat =*/ //noinspection ResultOfMethodCallIgnored
  226. LittleEndian.getUInt(data, offset); offset += LittleEndianConsts.INT_SIZE;
  227. long iTabID = LittleEndian.getUInt(data, offset); offset += LittleEndianConsts.INT_SIZE;
  228. //according to #2.4.304
  229. if (iTabID < 1 || iTabID > 0x0000FFFFL) {
  230. throw new XSSFBParseException("table id out of range: "+iTabID);
  231. }
  232. StringBuilder sb = new StringBuilder();
  233. offset += XSSFBUtils.readXLWideString(data, offset, sb);
  234. String relId = sb.toString(); sb.setLength(0);
  235. /*offset +=*/ XSSFBUtils.readXLWideString(data, offset, sb);
  236. String name = sb.toString();
  237. if (StringUtil.isNotBlank(relId)) {
  238. sheets.add(new XSSFSheetRef(relId, name));
  239. }
  240. }
  241. private boolean tryOldFormat(byte[] data) throws XSSFBParseException {
  242. //undocumented what is contained in these 8 bytes.
  243. //for the non-beta xlsb files, this would be 4, not 8.
  244. int offset = 8;
  245. long iTabID = LittleEndian.getUInt(data, offset); offset += LittleEndianConsts.INT_SIZE;
  246. if (iTabID < 1 || iTabID > 0x0000FFFFL) {
  247. throw new XSSFBParseException("table id out of range: "+iTabID);
  248. }
  249. StringBuilder sb = new StringBuilder();
  250. offset += XSSFBUtils.readXLWideString(data, offset, sb);
  251. String relId = sb.toString();
  252. sb.setLength(0);
  253. offset += XSSFBUtils.readXLWideString(data, offset, sb);
  254. String name = sb.toString();
  255. if (StringUtil.isNotBlank(relId)) {
  256. sheets.add(new XSSFSheetRef(relId, name));
  257. }
  258. return offset == data.length;
  259. }
  260. List<XSSFSheetRef> getSheets() {
  261. return sheets;
  262. }
  263. }
  264. }