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

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