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.

InternalSheet.java 58KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677
  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.hssf.model;
  16. import java.util.ArrayList;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import org.apache.logging.log4j.LogManager;
  20. import org.apache.logging.log4j.Logger;
  21. import org.apache.logging.log4j.message.SimpleMessage;
  22. import org.apache.poi.hssf.record.*;
  23. import org.apache.poi.hssf.record.aggregates.ChartSubstreamRecordAggregate;
  24. import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate;
  25. import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable;
  26. import org.apache.poi.hssf.record.aggregates.CustomViewSettingsRecordAggregate;
  27. import org.apache.poi.hssf.record.aggregates.DataValidityTable;
  28. import org.apache.poi.hssf.record.aggregates.MergedCellsTable;
  29. import org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
  30. import org.apache.poi.hssf.record.aggregates.RecordAggregate;
  31. import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor;
  32. import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
  33. import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate;
  34. import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
  35. import org.apache.poi.hssf.usermodel.HSSFSheet;
  36. import org.apache.poi.ss.formula.FormulaShifter;
  37. import org.apache.poi.ss.util.CellRangeAddress;
  38. import org.apache.poi.ss.util.PaneInformation;
  39. import org.apache.poi.util.Internal;
  40. import org.apache.poi.util.RecordFormatException;
  41. import static org.apache.logging.log4j.util.Unbox.box;
  42. /**
  43. * Low level model implementation of a Sheet (one workbook contains many sheets)
  44. * This file contains the low level binary records starting at the sheets BOF and
  45. * ending with the sheets EOF. Use HSSFSheet for a high level representation.
  46. * <P>
  47. * The structures of the highlevel API use references to this to perform most of their
  48. * operations. Its probably unwise to use these low level structures directly unless you
  49. * really know what you're doing. I recommend you read the Microsoft Excel 97 Developer's
  50. * Kit (Microsoft Press) and the documentation at http://sc.openoffice.org/excelfileformat.pdf
  51. * before even attempting to use this.
  52. *
  53. * @see InternalWorkbook
  54. * @see HSSFSheet
  55. */
  56. @Internal
  57. public final class InternalSheet {
  58. public static final short LeftMargin = 0;
  59. public static final short RightMargin = 1;
  60. public static final short TopMargin = 2;
  61. public static final short BottomMargin = 3;
  62. private static final Logger LOGGER = LogManager.getLogger(InternalSheet.class);
  63. private List<RecordBase> _records;
  64. protected PrintGridlinesRecord printGridlines;
  65. protected PrintHeadersRecord printHeaders;
  66. protected GridsetRecord gridset;
  67. private GutsRecord _gutsRecord;
  68. protected DefaultColWidthRecord defaultcolwidth = new DefaultColWidthRecord();
  69. protected DefaultRowHeightRecord defaultrowheight = new DefaultRowHeightRecord();
  70. private PageSettingsBlock _psBlock;
  71. /**
  72. * 'Worksheet Protection Block'<br>
  73. * Aggregate object is always present, but possibly empty.
  74. */
  75. private final WorksheetProtectionBlock _protectionBlock = new WorksheetProtectionBlock();
  76. protected WindowTwoRecord windowTwo;
  77. protected SelectionRecord _selection;
  78. /** java object always present, but if empty no BIFF records are written */
  79. private final MergedCellsTable _mergedCellsTable;
  80. /** always present in this POI object, not always written to Excel file */
  81. /*package*/ColumnInfoRecordsAggregate _columnInfos;
  82. /** the DimensionsRecord is always present */
  83. private DimensionsRecord _dimensions;
  84. /** always present */
  85. protected final RowRecordsAggregate _rowsAggregate;
  86. private DataValidityTable _dataValidityTable;
  87. private ConditionalFormattingTable condFormatting;
  88. private Iterator<RowRecord> rowRecIterator;
  89. /** Add an UncalcedRecord if not true indicating formulas have not been calculated */
  90. protected boolean _isUncalced;
  91. public static final byte PANE_LOWER_RIGHT = (byte)0;
  92. public static final byte PANE_UPPER_RIGHT = (byte)1;
  93. public static final byte PANE_LOWER_LEFT = (byte)2;
  94. public static final byte PANE_UPPER_LEFT = (byte)3;
  95. /**
  96. * read support (offset used as starting point for search) for low level
  97. * API. Pass in an array of Record objects, the sheet number (0 based) and
  98. * a record offset (should be the location of the sheets BOF record). A Sheet
  99. * object is constructed and passed back with all of its initialization set
  100. * to the passed in records and references to those records held. This function
  101. * is normally called via Workbook.
  102. *
  103. * @param rs the stream to read records from
  104. *
  105. * @return Sheet object with all values set to those read from the file
  106. *
  107. * @see InternalWorkbook
  108. * @see Record
  109. */
  110. public static InternalSheet createSheet(RecordStream rs) {
  111. return new InternalSheet(rs);
  112. }
  113. private InternalSheet(RecordStream rs) {
  114. _mergedCellsTable = new MergedCellsTable();
  115. RowRecordsAggregate rra = null;
  116. List<RecordBase> records = new ArrayList<>(128);
  117. _records = records; // needed here due to calls to findFirstRecordLocBySid before we're done
  118. int dimsloc = -1;
  119. if (rs.peekNextSid() != BOFRecord.sid) {
  120. throw new RecordFormatException("BOF record expected");
  121. }
  122. BOFRecord bof = (BOFRecord) rs.getNext();
  123. if (bof.getType() == BOFRecord.TYPE_WORKSHEET) {
  124. // Good, well supported
  125. } else if (bof.getType() == BOFRecord.TYPE_CHART ||
  126. bof.getType() == BOFRecord.TYPE_EXCEL_4_MACRO) {
  127. // These aren't really typical sheets... Let it go though,
  128. // we can handle them roughly well enough as a "normal" one
  129. } else {
  130. // Not a supported type
  131. // Skip onto the EOF, then complain
  132. while (rs.hasNext()) {
  133. Record rec = rs.getNext();
  134. if (rec instanceof EOFRecord) {
  135. break;
  136. }
  137. }
  138. throw new UnsupportedBOFType(bof.getType());
  139. }
  140. records.add(bof);
  141. while (rs.hasNext()) {
  142. int recSid = rs.peekNextSid();
  143. if ( recSid == CFHeaderRecord.sid || recSid == CFHeader12Record.sid ) {
  144. condFormatting = new ConditionalFormattingTable(rs);
  145. records.add(condFormatting);
  146. continue;
  147. }
  148. if (recSid == ColumnInfoRecord.sid) {
  149. _columnInfos = new ColumnInfoRecordsAggregate(rs);
  150. records.add(_columnInfos);
  151. continue;
  152. }
  153. if ( recSid == DVALRecord.sid) {
  154. _dataValidityTable = new DataValidityTable(rs);
  155. records.add(_dataValidityTable);
  156. continue;
  157. }
  158. if (RecordOrderer.isRowBlockRecord(recSid)) {
  159. //only add the aggregate once
  160. if (rra != null) {
  161. throw new RecordFormatException("row/cell records found in the wrong place");
  162. }
  163. RowBlocksReader rbr = new RowBlocksReader(rs);
  164. _mergedCellsTable.addRecords(rbr.getLooseMergedCells());
  165. rra = new RowRecordsAggregate(rbr.getPlainRecordStream(), rbr.getSharedFormulaManager());
  166. records.add(rra); //only add the aggregate once
  167. continue;
  168. }
  169. if (CustomViewSettingsRecordAggregate.isBeginRecord(recSid)) {
  170. // This happens three times in test sample file "29982.xls"
  171. // Also several times in bugzilla samples 46840-23373 and 46840-23374
  172. records.add(new CustomViewSettingsRecordAggregate(rs));
  173. continue;
  174. }
  175. if (PageSettingsBlock.isComponentRecord(recSid)) {
  176. if (_psBlock == null) {
  177. // first PSB record encountered - read all of them:
  178. _psBlock = new PageSettingsBlock(rs);
  179. records.add(_psBlock);
  180. } else {
  181. // one or more PSB records found after some intervening non-PSB records
  182. _psBlock.addLateRecords(rs);
  183. }
  184. // YK: in some cases records can be moved to the preceding
  185. // CustomViewSettingsRecordAggregate blocks
  186. _psBlock.positionRecords(records);
  187. continue;
  188. }
  189. if (WorksheetProtectionBlock.isComponentRecord(recSid)) {
  190. _protectionBlock.addRecords(rs);
  191. continue;
  192. }
  193. if (recSid == MergeCellsRecord.sid) {
  194. // when the MergedCellsTable is found in the right place, we expect those records to be contiguous
  195. _mergedCellsTable.read(rs);
  196. continue;
  197. }
  198. if (recSid == BOFRecord.sid) {
  199. ChartSubstreamRecordAggregate chartAgg = new ChartSubstreamRecordAggregate(rs);
  200. // if (false) {
  201. // // TODO - would like to keep the chart aggregate packed, but one unit test needs attention
  202. // records.add(chartAgg);
  203. // } else {
  204. spillAggregate(chartAgg, records);
  205. // }
  206. continue;
  207. }
  208. Record rec = rs.getNext();
  209. if ( recSid == IndexRecord.sid ) {
  210. // ignore INDEX record because it is only needed by Excel,
  211. // and POI always re-calculates its contents
  212. continue;
  213. }
  214. if (recSid == UncalcedRecord.sid) {
  215. // don't add UncalcedRecord to the list
  216. _isUncalced = true; // this flag is enough
  217. continue;
  218. }
  219. if (recSid == FeatRecord.sid ||
  220. recSid == FeatHdrRecord.sid) {
  221. records.add(rec);
  222. continue;
  223. }
  224. if (recSid == EOFRecord.sid) {
  225. records.add(rec);
  226. break;
  227. }
  228. if (recSid == DimensionsRecord.sid)
  229. {
  230. // Make a columns aggregate if one hasn't ready been created.
  231. if (_columnInfos == null)
  232. {
  233. _columnInfos = new ColumnInfoRecordsAggregate();
  234. records.add(_columnInfos);
  235. }
  236. _dimensions = ( DimensionsRecord ) rec;
  237. dimsloc = records.size();
  238. }
  239. else if (recSid == DefaultColWidthRecord.sid)
  240. {
  241. defaultcolwidth = ( DefaultColWidthRecord ) rec;
  242. }
  243. else if (recSid == DefaultRowHeightRecord.sid)
  244. {
  245. defaultrowheight = ( DefaultRowHeightRecord ) rec;
  246. }
  247. else if ( recSid == PrintGridlinesRecord.sid )
  248. {
  249. printGridlines = (PrintGridlinesRecord) rec;
  250. }
  251. else if ( recSid == PrintHeadersRecord.sid )
  252. {
  253. printHeaders = (PrintHeadersRecord) rec;
  254. }
  255. else if ( recSid == GridsetRecord.sid )
  256. {
  257. gridset = (GridsetRecord) rec;
  258. }
  259. else if ( recSid == SelectionRecord.sid )
  260. {
  261. _selection = (SelectionRecord) rec;
  262. }
  263. else if ( recSid == WindowTwoRecord.sid )
  264. {
  265. windowTwo = (WindowTwoRecord) rec;
  266. }
  267. else if ( recSid == GutsRecord.sid )
  268. {
  269. _gutsRecord = (GutsRecord) rec;
  270. }
  271. records.add(rec);
  272. }
  273. if (windowTwo == null) {
  274. throw new RecordFormatException("WINDOW2 was not found");
  275. }
  276. if (_dimensions == null) {
  277. // Excel seems to always write the DIMENSION record, but tolerates when it is not present
  278. // in all cases Excel (2007) adds the missing DIMENSION record
  279. if (rra == null) {
  280. // bug 46206 alludes to files which skip the DIMENSION record
  281. // when there are no row/cell records.
  282. // Not clear which application wrote these files.
  283. rra = new RowRecordsAggregate();
  284. } else {
  285. LOGGER.atWarn().log("DIMENSION record not found even though row/cells present");
  286. // Not sure if any tools write files like this, but Excel reads them OK
  287. }
  288. dimsloc = findFirstRecordLocBySid(WindowTwoRecord.sid);
  289. _dimensions = rra.createDimensions();
  290. records.add(dimsloc, _dimensions);
  291. }
  292. if (rra == null) {
  293. rra = new RowRecordsAggregate();
  294. records.add(dimsloc + 1, rra);
  295. }
  296. _rowsAggregate = rra;
  297. // put merged cells table in the right place (regardless of where the first MergedCellsRecord was found */
  298. RecordOrderer.addNewSheetRecord(records, _mergedCellsTable);
  299. RecordOrderer.addNewSheetRecord(records, _protectionBlock);
  300. LOGGER.atDebug().log("sheet createSheet (existing file) exited");
  301. }
  302. private static void spillAggregate(RecordAggregate ra, final List<RecordBase> recs) {
  303. ra.visitContainedRecords(r -> recs.add(r));
  304. }
  305. public static class UnsupportedBOFType extends RecordFormatException {
  306. private final int type;
  307. protected UnsupportedBOFType(int type) {
  308. super("BOF not of a supported type, found " + type);
  309. this.type = type;
  310. }
  311. public int getType() {
  312. return type;
  313. }
  314. }
  315. private static final class RecordCloner implements RecordVisitor {
  316. private final List<Record> _destList;
  317. public RecordCloner(List<Record> destList) {
  318. _destList = destList;
  319. }
  320. public void visitRecord(Record r) {
  321. _destList.add(r.copy());
  322. }
  323. }
  324. /**
  325. * Clones the low level records of this sheet and returns the new sheet instance.
  326. * This method is implemented by adding methods for deep cloning to all records that
  327. * can be added to a sheet. The <b>Record</b> object does not implement cloneable.
  328. * When adding a new record, implement a public clone method if and only if the record
  329. * belongs to a sheet.
  330. *
  331. * @return the cloned sheet
  332. */
  333. public InternalSheet cloneSheet() {
  334. List<Record> clonedRecords = new ArrayList<>(_records.size());
  335. for (int i = 0; i < _records.size(); i++) {
  336. RecordBase rb = _records.get(i);
  337. if (rb instanceof RecordAggregate) {
  338. ((RecordAggregate) rb).visitContainedRecords(new RecordCloner(clonedRecords));
  339. continue;
  340. }
  341. if (rb instanceof EscherAggregate){
  342. /**
  343. * this record will be removed after reading actual data from EscherAggregate
  344. */
  345. rb = new DrawingRecord();
  346. }
  347. Record rec = ((Record) rb).copy();
  348. clonedRecords.add(rec);
  349. }
  350. return createSheet(new RecordStream(clonedRecords, 0));
  351. }
  352. /**
  353. * Creates a sheet with all the usual records minus values and the "index"
  354. * record (not required). Sets the location pointer to where the first value
  355. * records should go. Use this to create a sheet from "scratch".
  356. *
  357. * @return Sheet object with all values set to defaults
  358. */
  359. public static InternalSheet createSheet() {
  360. return new InternalSheet();
  361. }
  362. private InternalSheet() {
  363. _mergedCellsTable = new MergedCellsTable();
  364. List<RecordBase> records = new ArrayList<>(32);
  365. LOGGER.atDebug().log("Sheet createsheet from scratch called");
  366. records.add(createBOF());
  367. records.add(createCalcMode());
  368. records.add(createCalcCount() );
  369. records.add(createRefMode() );
  370. records.add(createIteration() );
  371. records.add(createDelta() );
  372. records.add(createSaveRecalc() );
  373. printHeaders = createPrintHeaders();
  374. records.add(printHeaders);
  375. printGridlines = createPrintGridlines();
  376. records.add( printGridlines );
  377. gridset = createGridset();
  378. records.add( gridset );
  379. _gutsRecord = createGuts();
  380. records.add( _gutsRecord );
  381. defaultrowheight = createDefaultRowHeight();
  382. records.add( defaultrowheight );
  383. records.add( createWSBool() );
  384. // 'Page Settings Block'
  385. _psBlock = new PageSettingsBlock();
  386. records.add(_psBlock);
  387. // 'Worksheet Protection Block' (after 'Page Settings Block' and before DEFCOLWIDTH)
  388. records.add(_protectionBlock); // initially empty
  389. defaultcolwidth = createDefaultColWidth();
  390. records.add( defaultcolwidth);
  391. ColumnInfoRecordsAggregate columns = new ColumnInfoRecordsAggregate();
  392. records.add( columns );
  393. _columnInfos = columns;
  394. _dimensions = createDimensions();
  395. records.add(_dimensions);
  396. _rowsAggregate = new RowRecordsAggregate();
  397. records.add(_rowsAggregate);
  398. // 'Sheet View Settings'
  399. records.add(windowTwo = createWindowTwo());
  400. _selection = createSelection();
  401. records.add(_selection);
  402. records.add(_mergedCellsTable); // MCT comes after 'Sheet View Settings'
  403. records.add(EOFRecord.instance);
  404. _records = records;
  405. LOGGER.atDebug().log("Sheet createsheet from scratch exit");
  406. }
  407. public RowRecordsAggregate getRowsAggregate() {
  408. return _rowsAggregate;
  409. }
  410. private MergedCellsTable getMergedRecords() {
  411. // always present
  412. return _mergedCellsTable;
  413. }
  414. /**
  415. * Updates formulas in cells and conditional formats due to moving of cells
  416. *
  417. * @param shifter the formular shifter
  418. * @param externSheetIndex the externSheet index of this sheet
  419. */
  420. public void updateFormulasAfterCellShift(FormulaShifter shifter, int externSheetIndex) {
  421. getRowsAggregate().updateFormulasAfterRowShift(shifter, externSheetIndex);
  422. if (condFormatting != null) {
  423. getConditionalFormattingTable().updateFormulasAfterCellShift(shifter, externSheetIndex);
  424. }
  425. // TODO - adjust data validations
  426. }
  427. public int addMergedRegion(int rowFrom, int colFrom, int rowTo, int colTo) {
  428. // Validate input
  429. if (rowTo < rowFrom) {
  430. throw new IllegalArgumentException("The 'to' row (" + rowTo
  431. + ") must not be less than the 'from' row (" + rowFrom + ")");
  432. }
  433. if (colTo < colFrom) {
  434. throw new IllegalArgumentException("The 'to' col (" + colTo
  435. + ") must not be less than the 'from' col (" + colFrom + ")");
  436. }
  437. MergedCellsTable mrt = getMergedRecords();
  438. mrt.addArea(rowFrom, colFrom, rowTo, colTo);
  439. return mrt.getNumberOfMergedRegions()-1;
  440. }
  441. public void removeMergedRegion(int index) {
  442. //safety checks
  443. MergedCellsTable mrt = getMergedRecords();
  444. if (index >= mrt.getNumberOfMergedRegions()) {
  445. return;
  446. }
  447. mrt.remove(index);
  448. }
  449. public CellRangeAddress getMergedRegionAt(int index) {
  450. //safety checks
  451. MergedCellsTable mrt = getMergedRecords();
  452. if (index >= mrt.getNumberOfMergedRegions()) {
  453. return null;
  454. }
  455. return mrt.get(index);
  456. }
  457. public int getNumMergedRegions() {
  458. return getMergedRecords().getNumberOfMergedRegions();
  459. }
  460. public ConditionalFormattingTable getConditionalFormattingTable() {
  461. if (condFormatting == null) {
  462. condFormatting = new ConditionalFormattingTable();
  463. RecordOrderer.addNewSheetRecord(_records, condFormatting);
  464. }
  465. return condFormatting;
  466. }
  467. /**
  468. * Per an earlier reported bug in working with Andy Khan's excel read library. This
  469. * sets the values in the sheet's DimensionsRecord object to be correct. Excel doesn't
  470. * really care, but we want to play nice with other libraries.
  471. *
  472. * @param firstrow the first row index
  473. * @param firstcol the first column index
  474. * @param lastrow the last row index
  475. * @param lastcol the last column index
  476. *
  477. * @see DimensionsRecord
  478. */
  479. public void setDimensions(int firstrow, short firstcol, int lastrow, short lastcol)
  480. {
  481. LOGGER.atDebug().log("Sheet.setDimensions");
  482. LOGGER.atDebug().log(() -> new SimpleMessage(
  483. "firstrow" + firstrow +
  484. "firstcol" + firstcol +
  485. "lastrow" + lastrow +
  486. "lastcol" + lastcol
  487. ));
  488. _dimensions.setFirstCol(firstcol);
  489. _dimensions.setFirstRow(firstrow);
  490. _dimensions.setLastCol(lastcol);
  491. _dimensions.setLastRow(lastrow);
  492. LOGGER.atDebug().log("Sheet.setDimensions exiting");
  493. }
  494. public void visitContainedRecords(RecordVisitor rv, int offset) {
  495. PositionTrackingVisitor ptv = new PositionTrackingVisitor(rv, offset);
  496. boolean haveSerializedIndex = false;
  497. for (int k = 0; k < _records.size(); k++) {
  498. RecordBase record = _records.get(k);
  499. if (record instanceof RecordAggregate) {
  500. RecordAggregate agg = (RecordAggregate) record;
  501. agg.visitContainedRecords(ptv);
  502. } else {
  503. ptv.visitRecord((Record) record);
  504. }
  505. // If the BOF record was just serialized then add the IndexRecord
  506. if (record instanceof BOFRecord) {
  507. if (!haveSerializedIndex) {
  508. haveSerializedIndex = true;
  509. // Add an optional UncalcedRecord. However, we should add
  510. // it in only the once, after the sheet's own BOFRecord.
  511. // If there are diagrams, they have their own BOFRecords,
  512. // and one shouldn't go in after that!
  513. if (_isUncalced) {
  514. ptv.visitRecord(new UncalcedRecord());
  515. }
  516. //Can there be more than one BOF for a sheet? If not then we can
  517. //remove this guard. So be safe it is left here.
  518. if (_rowsAggregate != null) {
  519. // find forward distance to first RowRecord
  520. int initRecsSize = getSizeOfInitialSheetRecords(k);
  521. int currentPos = ptv.getPosition();
  522. ptv.visitRecord(_rowsAggregate.createIndexRecord(currentPos, initRecsSize));
  523. }
  524. }
  525. }
  526. }
  527. }
  528. /**
  529. * 'initial sheet records' are between INDEX and the 'Row Blocks'
  530. * @param bofRecordIndex index of record after which INDEX record is to be placed
  531. * @return count of bytes from end of INDEX record to first ROW record.
  532. */
  533. private int getSizeOfInitialSheetRecords(int bofRecordIndex) {
  534. int result = 0;
  535. // start just after BOF record (INDEX is not present in this list)
  536. for (int j = bofRecordIndex + 1; j < _records.size(); j++) {
  537. RecordBase tmpRec = _records.get(j);
  538. if (tmpRec instanceof RowRecordsAggregate) {
  539. break;
  540. }
  541. result += tmpRec.getRecordSize();
  542. }
  543. if (_isUncalced) {
  544. result += UncalcedRecord.getStaticRecordSize();
  545. }
  546. return result;
  547. }
  548. /**
  549. * Adds a value record to the sheet's contained binary records
  550. * (i.e. LabelSSTRecord or NumberRecord).
  551. * <P>
  552. * This method is "loc" sensitive. Meaning you need to set LOC to where you
  553. * want it to start searching. If you don't know do this: setLoc(getDimsLoc).
  554. * When adding several rows you can just start at the last one by leaving loc
  555. * at what this sets it to.
  556. *
  557. * @param row the row to add the cell value to
  558. * @param col the cell value record itself.
  559. */
  560. public void addValueRecord(int row, CellValueRecordInterface col) {
  561. LOGGER.atDebug().log("add value record row{}", box(row));
  562. DimensionsRecord d = _dimensions;
  563. if (col.getColumn() >= d.getLastCol()) {
  564. d.setLastCol(( short ) (col.getColumn() + 1));
  565. }
  566. if (col.getColumn() < d.getFirstCol()) {
  567. d.setFirstCol(col.getColumn());
  568. }
  569. _rowsAggregate.insertCell(col);
  570. }
  571. /**
  572. * remove a value record from the records array.
  573. *
  574. * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
  575. *
  576. * @param row - the row of the value record you wish to remove
  577. * @param col - a record supporting the CellValueRecordInterface.
  578. * @see CellValueRecordInterface
  579. */
  580. public void removeValueRecord(int row, CellValueRecordInterface col) {
  581. LOGGER.atDebug().log("remove value record row {}", box(row));
  582. _rowsAggregate.removeCell(col);
  583. }
  584. /**
  585. * replace a value record from the records array.
  586. *
  587. * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
  588. *
  589. * @param newval - a record supporting the CellValueRecordInterface. this will replace
  590. * the cell value with the same row and column. If there isn't one, one will
  591. * be added.
  592. */
  593. public void replaceValueRecord(CellValueRecordInterface newval) {
  594. LOGGER.atDebug().log("replaceValueRecord ");
  595. //The ValueRecordsAggregate use a tree map underneath.
  596. //The tree Map uses the CellValueRecordInterface as both the
  597. //key and the value, if we dont do a remove, then
  598. //the previous instance of the key is retained, effectively using
  599. //double the memory
  600. _rowsAggregate.removeCell(newval);
  601. _rowsAggregate.insertCell(newval);
  602. }
  603. /**
  604. * Adds a row record to the sheet
  605. *
  606. * <P>
  607. * This method is "loc" sensitive. Meaning you need to set LOC to where you
  608. * want it to start searching. If you don't know do this: setLoc(getDimsLoc).
  609. * When adding several rows you can just start at the last one by leaving loc
  610. * at what this sets it to.
  611. *
  612. * @param row the row record to be added
  613. */
  614. public void addRow(RowRecord row) {
  615. LOGGER.atDebug().log("addRow ");
  616. DimensionsRecord d = _dimensions;
  617. if (row.getRowNumber() >= d.getLastRow()) {
  618. d.setLastRow(row.getRowNumber() + 1);
  619. }
  620. if (row.getRowNumber() < d.getFirstRow()) {
  621. d.setFirstRow(row.getRowNumber());
  622. }
  623. //If the row exists remove it, so that any cells attached to the row are removed
  624. RowRecord existingRow = _rowsAggregate.getRow(row.getRowNumber());
  625. if (existingRow != null) {
  626. _rowsAggregate.removeRow(existingRow);
  627. }
  628. _rowsAggregate.insertRow(row);
  629. LOGGER.atDebug().log("exit addRow");
  630. }
  631. /**
  632. * Removes a row record
  633. *
  634. * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
  635. *
  636. * @param row the row record to remove
  637. */
  638. public void removeRow(RowRecord row) {
  639. _rowsAggregate.removeRow(row);
  640. }
  641. /**
  642. * Get all the value records (from LOC). Records will be returned from the first
  643. * record (starting at LOC) which is a value record.
  644. *
  645. * <P>
  646. * This method is "loc" sensitive. Meaning you need to set LOC to where you
  647. * want it to start searching. If you don't know do this: setLoc(getDimsLoc).
  648. * When adding several rows you can just start at the last one by leaving loc
  649. * at what this sets it to. For this method, set loc to dimsloc to start with,
  650. * subsequent calls will return values in (physical) sequence or NULL when you get to the end.
  651. *
  652. * @return Iterator of CellValueRecordInterface representing the value records
  653. */
  654. public Iterator<CellValueRecordInterface> getCellValueIterator(){
  655. return _rowsAggregate.getCellValueIterator();
  656. }
  657. /**
  658. * get the NEXT RowRecord (from LOC). The first record that is a Row record
  659. * (starting at LOC) will be returned.
  660. * <P>
  661. * This method is "loc" sensitive. Meaning you need to set LOC to where you
  662. * want it to start searching. If you don't know do this: setLoc(getDimsLoc).
  663. * When adding several rows you can just start at the last one by leaving loc
  664. * at what this sets it to. For this method, set loc to dimsloc to start with.
  665. * subsequent calls will return rows in (physical) sequence or NULL when you get to the end.
  666. *
  667. * @return RowRecord representing the next row record or NULL if there are no more
  668. */
  669. public RowRecord getNextRow() {
  670. if (rowRecIterator == null)
  671. {
  672. rowRecIterator = _rowsAggregate.getIterator();
  673. }
  674. if (!rowRecIterator.hasNext())
  675. {
  676. return null;
  677. }
  678. return rowRecIterator.next();
  679. }
  680. /**
  681. * get the NEXT (from LOC) RowRecord where rownumber matches the given rownum.
  682. * The first record that is a Row record (starting at LOC) that has the
  683. * same rownum as the given rownum will be returned.
  684. * <P>
  685. * This method is "loc" sensitive. Meaning you need to set LOC to where you
  686. * want it to start searching. If you don't know do this: setLoc(getDimsLoc).
  687. * When adding several rows you can just start at the last one by leaving loc
  688. * at what this sets it to. For this method, set loc to dimsloc to start with.
  689. * subsequent calls will return rows in (physical) sequence or NULL when you get to the end.
  690. *
  691. * @param rownum which row to return (careful with LOC)
  692. * @return RowRecord representing the next row record or NULL if there are no more
  693. *
  694. */
  695. public RowRecord getRow(int rownum) {
  696. return _rowsAggregate.getRow(rownum);
  697. }
  698. /**
  699. * creates the BOF record
  700. */
  701. /* package */ static BOFRecord createBOF() {
  702. BOFRecord retval = new BOFRecord();
  703. retval.setVersion(( short ) 0x600);
  704. retval.setType(( short ) 0x010);
  705. retval.setBuild(( short ) 0x0dbb);
  706. retval.setBuildYear(( short ) 1996);
  707. retval.setHistoryBitMask(0xc1);
  708. retval.setRequiredVersion(0x6);
  709. return retval;
  710. }
  711. /**
  712. * creates the CalcMode record and sets it to 1 (automatic formula caculation)
  713. */
  714. private static CalcModeRecord createCalcMode() {
  715. CalcModeRecord retval = new CalcModeRecord();
  716. retval.setCalcMode(( short ) 1);
  717. return retval;
  718. }
  719. /**
  720. * creates the CalcCount record and sets it to 100 (default number of iterations)
  721. */
  722. private static CalcCountRecord createCalcCount() {
  723. CalcCountRecord retval = new CalcCountRecord();
  724. retval.setIterations(( short ) 100); // default 100 iterations
  725. return retval;
  726. }
  727. /**
  728. * creates the RefMode record and sets it to A1 Mode (default reference mode)
  729. */
  730. private static RefModeRecord createRefMode() {
  731. RefModeRecord retval = new RefModeRecord();
  732. retval.setMode(RefModeRecord.USE_A1_MODE);
  733. return retval;
  734. }
  735. /**
  736. * creates the Iteration record and sets it to false (don't iteratively calculate formulas)
  737. */
  738. private static IterationRecord createIteration() {
  739. return new IterationRecord(false);
  740. }
  741. /**
  742. * creates the Delta record and sets it to 0.0010 (default accuracy)
  743. */
  744. private static DeltaRecord createDelta() {
  745. return new DeltaRecord(DeltaRecord.DEFAULT_VALUE);
  746. }
  747. /**
  748. * creates the SaveRecalc record and sets it to true (recalculate before saving)
  749. */
  750. private static SaveRecalcRecord createSaveRecalc() {
  751. SaveRecalcRecord retval = new SaveRecalcRecord();
  752. retval.setRecalc(true);
  753. return retval;
  754. }
  755. /**
  756. * creates the PrintHeaders record and sets it to false (we don't create headers yet so why print them)
  757. */
  758. private static PrintHeadersRecord createPrintHeaders() {
  759. PrintHeadersRecord retval = new PrintHeadersRecord();
  760. retval.setPrintHeaders(false);
  761. return retval;
  762. }
  763. /**
  764. * creates the PrintGridlines record and sets it to false (that makes for ugly sheets). As far as I can
  765. * tell this does the same thing as the GridsetRecord
  766. */
  767. private static PrintGridlinesRecord createPrintGridlines() {
  768. PrintGridlinesRecord retval = new PrintGridlinesRecord();
  769. retval.setPrintGridlines(false);
  770. return retval;
  771. }
  772. /**
  773. * creates the Gridset record and sets it to true (user has mucked with the gridlines)
  774. */
  775. private static GridsetRecord createGridset() {
  776. GridsetRecord retval = new GridsetRecord();
  777. retval.setGridset(true);
  778. return retval;
  779. }
  780. /**
  781. * creates the Guts record and sets leftrow/topcol guttter and rowlevelmax/collevelmax to 0
  782. */
  783. private static GutsRecord createGuts() {
  784. GutsRecord retval = new GutsRecord();
  785. retval.setLeftRowGutter(( short ) 0);
  786. retval.setTopColGutter(( short ) 0);
  787. retval.setRowLevelMax(( short ) 0);
  788. retval.setColLevelMax(( short ) 0);
  789. return retval;
  790. }
  791. private GutsRecord getGutsRecord() {
  792. if (_gutsRecord == null) {
  793. GutsRecord result = createGuts();
  794. RecordOrderer.addNewSheetRecord(_records, result);
  795. _gutsRecord = result;
  796. }
  797. return _gutsRecord;
  798. }
  799. /**
  800. * creates the DefaultRowHeight Record and sets its options to 0 and rowheight to 0xff
  801. */
  802. private static DefaultRowHeightRecord createDefaultRowHeight() {
  803. DefaultRowHeightRecord retval = new DefaultRowHeightRecord();
  804. retval.setOptionFlags(( short ) 0);
  805. retval.setRowHeight(DefaultRowHeightRecord.DEFAULT_ROW_HEIGHT);
  806. return retval;
  807. }
  808. /**
  809. * creates the WSBoolRecord and sets its values to defaults
  810. */
  811. private static WSBoolRecord createWSBool() {
  812. WSBoolRecord retval = new WSBoolRecord();
  813. retval.setWSBool1(( byte ) 0x4);
  814. retval.setWSBool2(( byte ) 0xffffffc1);
  815. return retval;
  816. }
  817. /**
  818. * creates the DefaultColWidth Record and sets it to 8
  819. */
  820. private static DefaultColWidthRecord createDefaultColWidth() {
  821. DefaultColWidthRecord retval = new DefaultColWidthRecord();
  822. retval.setColWidth(DefaultColWidthRecord.DEFAULT_COLUMN_WIDTH);
  823. return retval;
  824. }
  825. /**
  826. * get the default column width for the sheet (if the columns do not define their own width)
  827. * @return default column width
  828. */
  829. public int getDefaultColumnWidth() {
  830. return defaultcolwidth.getColWidth();
  831. }
  832. /**
  833. * @return <code>true</code> if gridlines are printed
  834. */
  835. public boolean isGridsPrinted() {
  836. if (gridset == null) {
  837. gridset = createGridset();
  838. //Insert the newlycreated Gridset record at the end of the record (just before the EOF)
  839. int loc = findFirstRecordLocBySid(EOFRecord.sid);
  840. _records.add(loc, gridset);
  841. }
  842. return !gridset.getGridset();
  843. }
  844. /**
  845. * set whether gridlines printed or not.
  846. * @param value True if gridlines printed.
  847. */
  848. public void setGridsPrinted(boolean value) {
  849. gridset.setGridset(!value);
  850. }
  851. /**
  852. * set the default column width for the sheet (if the columns do not define their own width)
  853. * @param dcw default column width
  854. */
  855. public void setDefaultColumnWidth(int dcw) {
  856. defaultcolwidth.setColWidth(dcw);
  857. }
  858. /**
  859. * set the default row height for the sheet (if the rows do not define their own height)
  860. *
  861. * @param dch the default row height
  862. */
  863. public void setDefaultRowHeight(short dch) {
  864. defaultrowheight.setRowHeight(dch);
  865. // set the bit that specifies that the default settings for the row height have been changed.
  866. defaultrowheight.setOptionFlags((short)1);
  867. }
  868. /**
  869. * get the default row height for the sheet (if the rows do not define their own height)
  870. * @return default row height
  871. */
  872. public short getDefaultRowHeight() {
  873. return defaultrowheight.getRowHeight();
  874. }
  875. /**
  876. * get the width of a given column in units of 1/256th of a character width
  877. * @param columnIndex index
  878. * @see DefaultColWidthRecord
  879. * @see ColumnInfoRecord
  880. * @see #setColumnWidth(int, int)
  881. * @return column width in units of 1/256th of a character width
  882. */
  883. public int getColumnWidth(int columnIndex) {
  884. ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex);
  885. if (ci != null) {
  886. return ci.getColumnWidth();
  887. }
  888. //default column width is measured in characters
  889. //multiply
  890. return (256*defaultcolwidth.getColWidth());
  891. }
  892. /**
  893. * get the index to the ExtendedFormatRecord "associated" with
  894. * the column at specified 0-based index. (In this case, an
  895. * ExtendedFormatRecord index is actually associated with a
  896. * ColumnInfoRecord which spans 1 or more columns)
  897. * <p>
  898. * Returns the index to the default ExtendedFormatRecord (0xF)
  899. * if no ColumnInfoRecord exists that includes the column
  900. * index specified.
  901. * @param columnIndex the column index
  902. * @return index of ExtendedFormatRecord associated with
  903. * ColumnInfoRecord that includes the column index or the
  904. * index of the default ExtendedFormatRecord (0xF)
  905. */
  906. public short getXFIndexForColAt(short columnIndex) {
  907. ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex);
  908. if (ci != null) {
  909. return (short)ci.getXFIndex();
  910. }
  911. return 0xF;
  912. }
  913. /**
  914. * set the width for a given column in 1/256th of a character width units
  915. *
  916. * @param column -
  917. * the column number
  918. * @param width
  919. * (in units of 1/256th of a character width)
  920. */
  921. public void setColumnWidth(int column, int width) {
  922. if(width > 255*256) throw new IllegalArgumentException("The maximum column width for an individual cell is 255 characters.");
  923. setColumn(column, null, Integer.valueOf(width), null, null, null);
  924. }
  925. /**
  926. * Get the hidden property for a given column.
  927. * @param columnIndex column index
  928. * @see DefaultColWidthRecord
  929. * @see ColumnInfoRecord
  930. * @see #setColumnHidden(int, boolean)
  931. * @return whether the column is hidden or not.
  932. */
  933. public boolean isColumnHidden(int columnIndex) {
  934. ColumnInfoRecord cir = _columnInfos.findColumnInfo(columnIndex);
  935. if (cir == null) {
  936. return false;
  937. }
  938. return cir.getHidden();
  939. }
  940. /**
  941. * Get the hidden property for a given column.
  942. * @param column - the column number
  943. * @param hidden - whether the column is hidden or not
  944. */
  945. public void setColumnHidden(int column, boolean hidden) {
  946. setColumn( column, null, null, null, Boolean.valueOf(hidden), null);
  947. }
  948. public void setDefaultColumnStyle(int column, int styleIndex) {
  949. setColumn(column, Short.valueOf((short)styleIndex), null, null, null, null);
  950. }
  951. private void setColumn(int column, Short xfStyle, Integer width, Integer level, Boolean hidden, Boolean collapsed) {
  952. _columnInfos.setColumn( column, xfStyle, width, level, hidden, collapsed );
  953. }
  954. /**
  955. * Creates an outline group for the specified columns.
  956. * @param fromColumn group from this column (inclusive)
  957. * @param toColumn group to this column (inclusive)
  958. * @param indent if true the group will be indented by one level,
  959. * if false indenting will be removed by one level.
  960. */
  961. public void groupColumnRange(int fromColumn, int toColumn, boolean indent) {
  962. // Set the level for each column
  963. _columnInfos.groupColumnRange( fromColumn, toColumn, indent);
  964. // Determine the maximum overall level
  965. int maxLevel = _columnInfos.getMaxOutlineLevel();
  966. GutsRecord guts = getGutsRecord();
  967. guts.setColLevelMax( (short) ( maxLevel+1 ) );
  968. if (maxLevel == 0) {
  969. guts.setTopColGutter( (short)0 );
  970. } else {
  971. guts.setTopColGutter( (short) ( 29 + (12 * (maxLevel-1)) ) );
  972. }
  973. }
  974. /**
  975. * creates the Dimensions Record and sets it to bogus values (you should set this yourself
  976. * or let the high level API do it for you)
  977. */
  978. private static DimensionsRecord createDimensions() {
  979. DimensionsRecord retval = new DimensionsRecord();
  980. retval.setFirstCol(( short ) 0);
  981. retval.setLastRow(1); // one more than it is
  982. retval.setFirstRow(0);
  983. retval.setLastCol(( short ) 1); // one more than it is
  984. return retval;
  985. }
  986. /**
  987. * creates the WindowTwo Record and sets it to: <P>
  988. * options = 0x6b6 <P>
  989. * toprow = 0 <P>
  990. * leftcol = 0 <P>
  991. * headercolor = 0x40 <P>
  992. * pagebreakzoom = 0x0 <P>
  993. * normalzoom = 0x0 <p>
  994. */
  995. private static WindowTwoRecord createWindowTwo() {
  996. WindowTwoRecord retval = new WindowTwoRecord();
  997. retval.setOptions(( short ) 0x6b6);
  998. retval.setTopRow(( short ) 0);
  999. retval.setLeftCol(( short ) 0);
  1000. retval.setHeaderColor(0x40);
  1001. retval.setPageBreakZoom(( short ) 0);
  1002. retval.setNormalZoom(( short ) 0);
  1003. return retval;
  1004. }
  1005. /**
  1006. * Creates the Selection record and sets it to nothing selected
  1007. */
  1008. private static SelectionRecord createSelection() {
  1009. return new SelectionRecord(0, 0);
  1010. }
  1011. public short getTopRow() {
  1012. return (windowTwo==null) ? (short) 0 : windowTwo.getTopRow();
  1013. }
  1014. public void setTopRow(short topRow) {
  1015. if (windowTwo!=null) {
  1016. windowTwo.setTopRow(topRow);
  1017. }
  1018. }
  1019. /**
  1020. * Sets the left column to show in desktop window pane.
  1021. * @param leftCol the left column to show in desktop window pane
  1022. */
  1023. public void setLeftCol(short leftCol) {
  1024. if (windowTwo!=null) {
  1025. windowTwo.setLeftCol(leftCol);
  1026. }
  1027. }
  1028. public short getLeftCol() {
  1029. return (windowTwo==null) ? (short) 0 : windowTwo.getLeftCol();
  1030. }
  1031. /**
  1032. * Returns the active row
  1033. *
  1034. * @see SelectionRecord
  1035. * @return row the active row index
  1036. */
  1037. public int getActiveCellRow() {
  1038. if (_selection == null) {
  1039. return 0;
  1040. }
  1041. return _selection.getActiveCellRow();
  1042. }
  1043. /**
  1044. * Sets the active row
  1045. *
  1046. * @param row the row index
  1047. * @see SelectionRecord
  1048. */
  1049. public void setActiveCellRow(int row) {
  1050. //shouldn't have a sheet w/o a SelectionRecord, but best to guard anyway
  1051. if (_selection != null) {
  1052. _selection.setActiveCellRow(row);
  1053. }
  1054. }
  1055. /**
  1056. * @see SelectionRecord
  1057. * @return column of the active cell
  1058. */
  1059. public short getActiveCellCol() {
  1060. if (_selection == null) {
  1061. return 0;
  1062. }
  1063. return (short)_selection.getActiveCellCol();
  1064. }
  1065. /**
  1066. * Sets the active column
  1067. *
  1068. * @param col the column index
  1069. * @see SelectionRecord
  1070. */
  1071. public void setActiveCellCol(short col) {
  1072. //shouldn't have a sheet w/o a SelectionRecord, but best to guard anyway
  1073. if (_selection != null)
  1074. {
  1075. _selection.setActiveCellCol(col);
  1076. }
  1077. }
  1078. public List<RecordBase> getRecords() {
  1079. return _records;
  1080. }
  1081. /**
  1082. * Gets the gridset record for this sheet.
  1083. *
  1084. * @return the gridset record for this sheet
  1085. */
  1086. public GridsetRecord getGridsetRecord()
  1087. {
  1088. return gridset;
  1089. }
  1090. /**
  1091. * Returns the first occurrence of a record matching a particular sid.
  1092. *
  1093. * @param sid the sid to search for
  1094. *
  1095. * @return the matching record or {@code null} if it wasn't found
  1096. */
  1097. public Record findFirstRecordBySid(short sid) {
  1098. int ix = findFirstRecordLocBySid(sid);
  1099. if (ix < 0) {
  1100. return null;
  1101. }
  1102. return (Record) _records.get(ix);
  1103. }
  1104. /**
  1105. * Sets the SCL record or creates it in the correct place if it does not
  1106. * already exist.
  1107. *
  1108. * @param sclRecord The record to set.
  1109. */
  1110. public void setSCLRecord(SCLRecord sclRecord) {
  1111. int oldRecordLoc = findFirstRecordLocBySid(SCLRecord.sid);
  1112. if (oldRecordLoc == -1) {
  1113. // Insert it after the window record
  1114. int windowRecordLoc = findFirstRecordLocBySid(WindowTwoRecord.sid);
  1115. _records.add(windowRecordLoc+1, sclRecord);
  1116. } else {
  1117. _records.set(oldRecordLoc, sclRecord);
  1118. }
  1119. }
  1120. /**
  1121. * Finds the first occurrence of a record matching a particular sid and
  1122. * returns it's position.
  1123. * @param sid the sid to search for
  1124. * @return the record position of the matching record or -1 if no match
  1125. * is made.
  1126. */
  1127. public int findFirstRecordLocBySid( short sid ) { // TODO - remove this method
  1128. int max = _records.size();
  1129. for (int i=0; i< max; i++) {
  1130. Object rb = _records.get(i);
  1131. if (!(rb instanceof Record)) {
  1132. continue;
  1133. }
  1134. Record record = (Record) rb;
  1135. if (record.getSid() == sid) {
  1136. return i;
  1137. }
  1138. }
  1139. return -1;
  1140. }
  1141. public WindowTwoRecord getWindowTwo() {
  1142. return windowTwo;
  1143. }
  1144. /**
  1145. * Returns the PrintGridlinesRecord.
  1146. * @return PrintGridlinesRecord for the sheet.
  1147. */
  1148. public PrintGridlinesRecord getPrintGridlines ()
  1149. {
  1150. return printGridlines;
  1151. }
  1152. /**
  1153. * Sets the PrintGridlinesRecord.
  1154. * @param newPrintGridlines The new PrintGridlinesRecord for the sheet.
  1155. */
  1156. public void setPrintGridlines (PrintGridlinesRecord newPrintGridlines)
  1157. {
  1158. printGridlines = newPrintGridlines;
  1159. }
  1160. /**
  1161. * Returns the PrintHeadersRecord.
  1162. * @return PrintHeadersRecord for the sheet.
  1163. */
  1164. public PrintHeadersRecord getPrintHeaders()
  1165. {
  1166. return printHeaders;
  1167. }
  1168. /**
  1169. * Sets the PrintHeadersRecord.
  1170. * @param newPrintHeaders The new PrintHeadersRecord for the sheet.
  1171. */
  1172. public void setPrintHeaders(PrintHeadersRecord newPrintHeaders)
  1173. {
  1174. printHeaders = newPrintHeaders;
  1175. }
  1176. /**
  1177. * Sets whether the sheet is selected
  1178. * @param sel True to select the sheet, false otherwise.
  1179. */
  1180. public void setSelected(boolean sel) {
  1181. windowTwo.setSelected(sel);
  1182. }
  1183. /**
  1184. * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
  1185. *
  1186. * <p>If both colSplit and rowSplit are zero then the existing freeze pane is removed</p>
  1187. *
  1188. * @param colSplit Horizonatal position of split.
  1189. * @param rowSplit Vertical position of split.
  1190. * @param topRow Top row visible in bottom pane
  1191. * @param leftmostColumn Left column visible in right pane.
  1192. */
  1193. public void createFreezePane(int colSplit, int rowSplit, int topRow, int leftmostColumn) {
  1194. int paneLoc = findFirstRecordLocBySid(PaneRecord.sid);
  1195. if (paneLoc != -1)
  1196. _records.remove(paneLoc);
  1197. // If both colSplit and rowSplit are zero then the existing freeze pane is removed
  1198. if(colSplit == 0 && rowSplit == 0){
  1199. windowTwo.setFreezePanes(false);
  1200. windowTwo.setFreezePanesNoSplit(false);
  1201. SelectionRecord sel = (SelectionRecord) findFirstRecordBySid(SelectionRecord.sid);
  1202. if (sel != null) {
  1203. sel.setPane(PaneInformation.PANE_UPPER_LEFT);
  1204. }
  1205. return;
  1206. }
  1207. int loc = findFirstRecordLocBySid(WindowTwoRecord.sid);
  1208. PaneRecord pane = new PaneRecord();
  1209. pane.setX((short)colSplit);
  1210. pane.setY((short)rowSplit);
  1211. pane.setTopRow((short) topRow);
  1212. pane.setLeftColumn((short) leftmostColumn);
  1213. if (rowSplit == 0) {
  1214. pane.setTopRow((short)0);
  1215. pane.setActivePane((short)1);
  1216. } else if (colSplit == 0) {
  1217. pane.setLeftColumn((short)0);
  1218. pane.setActivePane((short)2);
  1219. } else {
  1220. pane.setActivePane((short)0);
  1221. }
  1222. _records.add(loc+1, pane);
  1223. windowTwo.setFreezePanes(true);
  1224. windowTwo.setFreezePanesNoSplit(true);
  1225. SelectionRecord sel = (SelectionRecord) findFirstRecordBySid(SelectionRecord.sid);
  1226. if (sel != null) {
  1227. sel.setPane((byte) pane.getActivePane());
  1228. }
  1229. }
  1230. /**
  1231. * Creates a split pane. Any existing freezepane or split pane is overwritten.
  1232. * @param xSplitPos Horizonatal position of split (in 1/20th of a point).
  1233. * @param ySplitPos Vertical position of split (in 1/20th of a point).
  1234. * @param topRow Top row visible in bottom pane
  1235. * @param leftmostColumn Left column visible in right pane.
  1236. * @param activePane Active pane. One of: PANE_LOWER_RIGHT,
  1237. * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT
  1238. * @see #PANE_LOWER_LEFT
  1239. * @see #PANE_LOWER_RIGHT
  1240. * @see #PANE_UPPER_LEFT
  1241. * @see #PANE_UPPER_RIGHT
  1242. */
  1243. public void createSplitPane(int xSplitPos, int ySplitPos, int topRow, int leftmostColumn, int activePane) {
  1244. int paneLoc = findFirstRecordLocBySid(PaneRecord.sid);
  1245. if (paneLoc != -1)
  1246. _records.remove(paneLoc);
  1247. int loc = findFirstRecordLocBySid(WindowTwoRecord.sid);
  1248. PaneRecord r = new PaneRecord();
  1249. r.setX((short)xSplitPos);
  1250. r.setY((short)ySplitPos);
  1251. r.setTopRow((short) topRow);
  1252. r.setLeftColumn((short) leftmostColumn);
  1253. r.setActivePane((short) activePane);
  1254. _records.add(loc+1, r);
  1255. windowTwo.setFreezePanes(false);
  1256. windowTwo.setFreezePanesNoSplit(false);
  1257. SelectionRecord sel = (SelectionRecord) findFirstRecordBySid(SelectionRecord.sid);
  1258. if (sel != null) {
  1259. sel.setPane(PANE_LOWER_RIGHT);
  1260. }
  1261. }
  1262. /**
  1263. * Returns the information regarding the currently configured pane (split or freeze).
  1264. * @return <code>null</code> if no pane configured, or the pane information.
  1265. */
  1266. public PaneInformation getPaneInformation() {
  1267. PaneRecord rec = (PaneRecord)findFirstRecordBySid(PaneRecord.sid);
  1268. if (rec == null)
  1269. return null;
  1270. return new PaneInformation(rec.getX(), rec.getY(), rec.getTopRow(),
  1271. rec.getLeftColumn(), (byte)rec.getActivePane(), windowTwo.getFreezePanes());
  1272. }
  1273. public SelectionRecord getSelection() {
  1274. return _selection;
  1275. }
  1276. public void setSelection( SelectionRecord selection) {
  1277. _selection = selection;
  1278. }
  1279. /**
  1280. * @return the {@link WorksheetProtectionBlock} for this sheet
  1281. */
  1282. public WorksheetProtectionBlock getProtectionBlock() {
  1283. return _protectionBlock;
  1284. }
  1285. /**
  1286. * Sets whether the gridlines are shown in a viewer.
  1287. * @param show whether to show gridlines or not
  1288. */
  1289. public void setDisplayGridlines(boolean show) {
  1290. windowTwo.setDisplayGridlines(show);
  1291. }
  1292. /**
  1293. * @return <code>true</code> if gridlines are displayed
  1294. */
  1295. public boolean isDisplayGridlines() {
  1296. return windowTwo.getDisplayGridlines();
  1297. }
  1298. /**
  1299. * Sets whether the formulas are shown in a viewer.
  1300. * @param show whether to show formulas or not
  1301. */
  1302. public void setDisplayFormulas(boolean show) {
  1303. windowTwo.setDisplayFormulas(show);
  1304. }
  1305. /**
  1306. * Returns if formulas are displayed.
  1307. * @return whether formulas are displayed
  1308. */
  1309. public boolean isDisplayFormulas() {
  1310. return windowTwo.getDisplayFormulas();
  1311. }
  1312. /**
  1313. * Sets whether the RowColHeadings are shown in a viewer.
  1314. * @param show whether to show RowColHeadings or not
  1315. */
  1316. public void setDisplayRowColHeadings(boolean show) {
  1317. windowTwo.setDisplayRowColHeadings(show);
  1318. }
  1319. /**
  1320. * Returns if RowColHeadings are displayed.
  1321. * @return whether RowColHeadings are displayed
  1322. */
  1323. public boolean isDisplayRowColHeadings() {
  1324. return windowTwo.getDisplayRowColHeadings();
  1325. }
  1326. /**
  1327. * Sets whether the RowColHeadings are shown in a viewer.
  1328. * @param show whether to show RowColHeadings or not
  1329. */
  1330. public void setPrintRowColHeadings(boolean show) {
  1331. windowTwo.setDisplayRowColHeadings(show);
  1332. }
  1333. /**
  1334. * Returns if RowColHeadings are displayed.
  1335. * @return whether RowColHeadings are displayed
  1336. */
  1337. public boolean isPrintRowColHeadings() {
  1338. return windowTwo.getDisplayRowColHeadings();
  1339. }
  1340. /**
  1341. * @return whether an uncalced record must be inserted or not at generation
  1342. */
  1343. public boolean getUncalced() {
  1344. return _isUncalced;
  1345. }
  1346. /**
  1347. * @param uncalced whether an uncalced record must be inserted or not at generation
  1348. */
  1349. public void setUncalced(boolean uncalced) {
  1350. this._isUncalced = uncalced;
  1351. }
  1352. /**
  1353. * Finds the DrawingRecord for our sheet, and
  1354. * attaches it to the DrawingManager (which knows about
  1355. * the overall DrawingGroup for our workbook).
  1356. * If requested, will create a new DrawRecord
  1357. * if none currently exist
  1358. * @param drawingManager The DrawingManager2 for our workbook
  1359. * @param createIfMissing Should one be created if missing?
  1360. * @return location of EscherAggregate record. if no EscherAggregate record is found return -1
  1361. */
  1362. public int aggregateDrawingRecords(DrawingManager2 drawingManager, boolean createIfMissing) {
  1363. int loc = findFirstRecordLocBySid(DrawingRecord.sid);
  1364. boolean noDrawingRecordsFound = (loc == -1);
  1365. if (noDrawingRecordsFound) {
  1366. if(!createIfMissing) {
  1367. // None found, and not allowed to add in
  1368. return -1;
  1369. }
  1370. EscherAggregate aggregate = new EscherAggregate(true);
  1371. loc = findFirstRecordLocBySid(EscherAggregate.sid);
  1372. if (loc == -1) {
  1373. loc = findFirstRecordLocBySid( WindowTwoRecord.sid );
  1374. } else {
  1375. getRecords().remove(loc);
  1376. }
  1377. getRecords().add( loc, aggregate );
  1378. return loc;
  1379. }
  1380. List<RecordBase> records = getRecords();
  1381. EscherAggregate.createAggregate(records, loc);
  1382. return loc;
  1383. }
  1384. /**
  1385. * Perform any work necessary before the sheet is about to be serialized.
  1386. * For instance the escher aggregates size needs to be calculated before
  1387. * serialization so that the dgg record (which occurs first) can be written.
  1388. */
  1389. public void preSerialize() {
  1390. for (RecordBase r: getRecords()) {
  1391. if (r instanceof EscherAggregate) {
  1392. // Trigger flattening of user model and corresponding update of dgg record.
  1393. r.getRecordSize();
  1394. }
  1395. }
  1396. }
  1397. public PageSettingsBlock getPageSettings() {
  1398. if (_psBlock == null) {
  1399. _psBlock = new PageSettingsBlock();
  1400. RecordOrderer.addNewSheetRecord(_records, _psBlock);
  1401. }
  1402. return _psBlock;
  1403. }
  1404. public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) {
  1405. if (collapsed) {
  1406. _columnInfos.collapseColumn(columnNumber);
  1407. } else {
  1408. _columnInfos.expandColumn(columnNumber);
  1409. }
  1410. }
  1411. public void groupRowRange(int fromRow, int toRow, boolean indent)
  1412. {
  1413. for (int rowNum = fromRow; rowNum <= toRow; rowNum++)
  1414. {
  1415. RowRecord row = getRow( rowNum );
  1416. if (row == null)
  1417. {
  1418. row = RowRecordsAggregate.createRow(rowNum);
  1419. addRow( row );
  1420. }
  1421. int level = row.getOutlineLevel();
  1422. if (indent) level++; else level--;
  1423. level = Math.max(0, level);
  1424. level = Math.min(7, level);
  1425. row.setOutlineLevel((short) ( level ));
  1426. }
  1427. recalcRowGutter();
  1428. }
  1429. private void recalcRowGutter() {
  1430. int maxLevel = 0;
  1431. Iterator<RowRecord> iterator = _rowsAggregate.getIterator();
  1432. while (iterator.hasNext()) {
  1433. RowRecord rowRecord = iterator.next();
  1434. maxLevel = Math.max(rowRecord.getOutlineLevel(), maxLevel);
  1435. }
  1436. // Grab the guts record, adding if needed
  1437. GutsRecord guts = getGutsRecord();
  1438. // Set the levels onto it
  1439. guts.setRowLevelMax( (short) ( maxLevel + 1 ) );
  1440. guts.setLeftRowGutter( (short) ( 29 + (12 * (maxLevel)) ) );
  1441. }
  1442. public DataValidityTable getOrCreateDataValidityTable() {
  1443. if (_dataValidityTable == null) {
  1444. DataValidityTable result = new DataValidityTable();
  1445. RecordOrderer.addNewSheetRecord(_records, result);
  1446. _dataValidityTable = result;
  1447. }
  1448. return _dataValidityTable;
  1449. }
  1450. /**
  1451. * Get the {@link NoteRecord}s (related to cell comments) for this sheet
  1452. * @return never <code>null</code>, typically empty array
  1453. */
  1454. public NoteRecord[] getNoteRecords() {
  1455. List<NoteRecord> temp = new ArrayList<>();
  1456. for(int i=_records.size()-1; i>=0; i--) {
  1457. RecordBase rec = _records.get(i);
  1458. if (rec instanceof NoteRecord) {
  1459. temp.add((NoteRecord) rec);
  1460. }
  1461. }
  1462. if (temp.size() < 1) {
  1463. return NoteRecord.EMPTY_ARRAY;
  1464. }
  1465. NoteRecord[] result = new NoteRecord[temp.size()];
  1466. temp.toArray(result);
  1467. return result;
  1468. }
  1469. public int getColumnOutlineLevel(int columnIndex) {
  1470. return _columnInfos.getOutlineLevel(columnIndex);
  1471. }
  1472. public int getMinColumnIndex() {
  1473. return _columnInfos.getMinColumnIndex();
  1474. }
  1475. public int getMaxColumnIndex() {
  1476. return _columnInfos.getMaxColumnIndex();
  1477. }
  1478. }