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.

XSSFSheet.java 196KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.usermodel;
  16. import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
  17. import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword;
  18. import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.util.*;
  23. import javax.xml.namespace.QName;
  24. import javax.xml.stream.XMLStreamException;
  25. import javax.xml.stream.XMLStreamReader;
  26. import org.apache.logging.log4j.LogManager;
  27. import org.apache.logging.log4j.Logger;
  28. import org.apache.poi.ooxml.POIXMLDocumentPart;
  29. import org.apache.poi.ooxml.POIXMLException;
  30. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  31. import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException;
  32. import org.apache.poi.openxml4j.opc.*;
  33. import org.apache.poi.poifs.crypt.HashAlgorithm;
  34. import org.apache.poi.ss.SpreadsheetVersion;
  35. import org.apache.poi.ss.formula.FormulaShifter;
  36. import org.apache.poi.ss.formula.SheetNameFormatter;
  37. import org.apache.poi.ss.usermodel.*;
  38. import org.apache.poi.ss.util.AreaReference;
  39. import org.apache.poi.ss.util.CellAddress;
  40. import org.apache.poi.ss.util.CellRangeAddress;
  41. import org.apache.poi.ss.util.CellRangeAddressList;
  42. import org.apache.poi.ss.util.CellReference;
  43. import org.apache.poi.ss.util.PaneInformation;
  44. import org.apache.poi.ss.util.SSCellRange;
  45. import org.apache.poi.ss.util.SheetUtil;
  46. import org.apache.poi.util.Beta;
  47. import org.apache.poi.util.Internal;
  48. import org.apache.poi.util.Removal;
  49. import org.apache.poi.util.Units;
  50. import org.apache.poi.xssf.model.Comments;
  51. import org.apache.poi.xssf.usermodel.XSSFPivotTable.PivotTableReferenceConfigurator;
  52. import org.apache.poi.xssf.usermodel.helpers.ColumnHelper;
  53. import org.apache.poi.xssf.usermodel.helpers.XSSFColumnShifter;
  54. import org.apache.poi.xssf.usermodel.helpers.XSSFIgnoredErrorHelper;
  55. import org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter;
  56. import org.apache.xmlbeans.XmlCursor;
  57. import org.apache.xmlbeans.XmlException;
  58. import org.apache.xmlbeans.XmlObject;
  59. import org.apache.xmlbeans.XmlOptions;
  60. import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
  61. /**
  62. * High level representation of a SpreadsheetML worksheet.
  63. *
  64. * <p>
  65. * Sheets are the central structures within a workbook, and are where a user does most of his spreadsheet work.
  66. * The most common type of sheet is the worksheet, which is represented as a grid of cells. Worksheet cells can
  67. * contain text, numbers, dates, and formulas. Cells can also be formatted.
  68. * </p>
  69. */
  70. public class XSSFSheet extends POIXMLDocumentPart implements Sheet, OoxmlSheetExtensions {
  71. private static final Logger LOG = LogManager.getLogger(XSSFSheet.class);
  72. private static final double DEFAULT_ROW_HEIGHT = 15.0;
  73. private static final double DEFAULT_MARGIN_HEADER = 0.3;
  74. private static final double DEFAULT_MARGIN_FOOTER = 0.3;
  75. private static final double DEFAULT_MARGIN_TOP = 0.75;
  76. private static final double DEFAULT_MARGIN_BOTTOM = 0.75;
  77. private static final double DEFAULT_MARGIN_LEFT = 0.7;
  78. private static final double DEFAULT_MARGIN_RIGHT = 0.7;
  79. //TODO make the two variable below private!
  80. protected CTSheet sheet;
  81. protected CTWorksheet worksheet;
  82. private final SortedMap<Integer, XSSFRow> _rows = new TreeMap<>();
  83. private List<XSSFHyperlink> hyperlinks;
  84. private ColumnHelper columnHelper;
  85. private Comments sheetComments;
  86. /**
  87. * cache of master shared formulas in this sheet.
  88. * Master shared formula is the first formula in a group of shared formulas is saved in the f element.
  89. */
  90. private Map<Integer, CTCellFormula> sharedFormulas;
  91. private SortedMap<String,XSSFTable> tables;
  92. private List<CellRangeAddress> arrayFormulas;
  93. private final XSSFDataValidationHelper dataValidationHelper;
  94. private XSSFVMLDrawing xssfvmlDrawing;
  95. private CellRangeAddress dimensionOverride;
  96. /**
  97. * Creates new XSSFSheet - called by XSSFWorkbook to create a sheet from scratch.
  98. *
  99. * @see XSSFWorkbook#createSheet()
  100. */
  101. protected XSSFSheet() {
  102. super();
  103. dataValidationHelper = new XSSFDataValidationHelper(this);
  104. onDocumentCreate();
  105. }
  106. /**
  107. * Creates an XSSFSheet representing the given package part and relationship.
  108. * Should only be called by XSSFWorkbook when reading in an existing file.
  109. *
  110. * @param part - The package part that holds xml data representing this sheet.
  111. *
  112. * @since POI 3.14-Beta1
  113. */
  114. protected XSSFSheet(PackagePart part) {
  115. super(part);
  116. dataValidationHelper = new XSSFDataValidationHelper(this);
  117. }
  118. /**
  119. * Returns the parent XSSFWorkbook
  120. *
  121. * @return the parent XSSFWorkbook
  122. */
  123. @Override
  124. public XSSFWorkbook getWorkbook() {
  125. return (XSSFWorkbook)getParent();
  126. }
  127. /**
  128. * Initialize worksheet data when reading in an exisiting file.
  129. */
  130. @Override
  131. protected void onDocumentRead() {
  132. try (InputStream stream = getPackagePart().getInputStream()) {
  133. read(stream);
  134. } catch (IOException | ArithmeticException e){
  135. throw new POIXMLException(e);
  136. }
  137. }
  138. protected void read(InputStream is) throws IOException {
  139. try {
  140. worksheet = WorksheetDocument.Factory.parse(is, DEFAULT_XML_OPTIONS).getWorksheet();
  141. } catch (XmlException e){
  142. throw new POIXMLException(e);
  143. }
  144. columnHelper = new ColumnHelper(worksheet);
  145. initRows(worksheet);
  146. // Look for bits we're interested in
  147. for(RelationPart rp : getRelationParts()){
  148. POIXMLDocumentPart p = rp.getDocumentPart();
  149. if(p instanceof Comments) {
  150. sheetComments = (Comments)p;
  151. sheetComments.setSheet(this);
  152. }
  153. if(p instanceof XSSFTable) {
  154. tables.put( rp.getRelationship().getId(), (XSSFTable)p );
  155. }
  156. if(p instanceof XSSFPivotTable) {
  157. getWorkbook().getPivotTables().add((XSSFPivotTable) p);
  158. }
  159. }
  160. // Process external hyperlinks for the sheet, if there are any
  161. initHyperlinks();
  162. }
  163. /**
  164. * Initialize worksheet data when creating a new sheet.
  165. */
  166. @Override
  167. protected void onDocumentCreate(){
  168. worksheet = newSheet();
  169. initRows(worksheet);
  170. columnHelper = new ColumnHelper(worksheet);
  171. hyperlinks = new ArrayList<>();
  172. }
  173. private void initRows(CTWorksheet worksheetParam) {
  174. if (worksheetParam.getSheetData() == null || worksheetParam.getSheetData().getRowArray() == null) {
  175. throw new IllegalArgumentException("Had empty sheet data when initializing the sheet");
  176. }
  177. _rows.clear();
  178. tables = new TreeMap<>();
  179. sharedFormulas = new HashMap<>();
  180. arrayFormulas = new ArrayList<>();
  181. for (CTRow row : worksheetParam.getSheetData().getRowArray()) {
  182. XSSFRow r = new XSSFRow(row, this);
  183. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  184. //noinspection UnnecessaryBoxing
  185. final Integer rownumI = Integer.valueOf(r.getRowNum()); // NOSONAR
  186. _rows.put(rownumI, r);
  187. }
  188. }
  189. /**
  190. * Read hyperlink relations, link them with CTHyperlink beans in this worksheet
  191. * and initialize the internal array of XSSFHyperlink objects
  192. */
  193. private void initHyperlinks() {
  194. hyperlinks = new ArrayList<>();
  195. if(!worksheet.isSetHyperlinks()) {
  196. return;
  197. }
  198. try {
  199. PackageRelationshipCollection hyperRels =
  200. getPackagePart().getRelationshipsByType(XSSFRelation.SHEET_HYPERLINKS.getRelation());
  201. // Turn each one into a XSSFHyperlink
  202. for(CTHyperlink hyperlink : worksheet.getHyperlinks().getHyperlinkArray()) {
  203. PackageRelationship hyperRel = null;
  204. if(hyperlink.getId() != null) {
  205. hyperRel = hyperRels.getRelationshipByID(hyperlink.getId());
  206. }
  207. hyperlinks.add( new XSSFHyperlink(hyperlink, hyperRel) );
  208. }
  209. } catch (InvalidFormatException e){
  210. throw new POIXMLException(e);
  211. }
  212. }
  213. /**
  214. * Create a new CTWorksheet instance with all values set to defaults
  215. *
  216. * @return a new instance
  217. */
  218. private static CTWorksheet newSheet(){
  219. CTWorksheet worksheet = CTWorksheet.Factory.newInstance();
  220. CTSheetFormatPr ctFormat = worksheet.addNewSheetFormatPr();
  221. ctFormat.setDefaultRowHeight(DEFAULT_ROW_HEIGHT);
  222. CTSheetView ctView = worksheet.addNewSheetViews().addNewSheetView();
  223. ctView.setWorkbookViewId(0);
  224. worksheet.addNewDimension().setRef("A1");
  225. worksheet.addNewSheetData();
  226. CTPageMargins ctMargins = worksheet.addNewPageMargins();
  227. ctMargins.setBottom(DEFAULT_MARGIN_BOTTOM);
  228. ctMargins.setFooter(DEFAULT_MARGIN_FOOTER);
  229. ctMargins.setHeader(DEFAULT_MARGIN_HEADER);
  230. ctMargins.setLeft(DEFAULT_MARGIN_LEFT);
  231. ctMargins.setRight(DEFAULT_MARGIN_RIGHT);
  232. ctMargins.setTop(DEFAULT_MARGIN_TOP);
  233. return worksheet;
  234. }
  235. /**
  236. * Provide access to the CTWorksheet bean holding this sheet's data
  237. *
  238. * @return the CTWorksheet bean holding this sheet's data
  239. */
  240. @Internal
  241. public CTWorksheet getCTWorksheet() {
  242. return this.worksheet;
  243. }
  244. public ColumnHelper getColumnHelper() {
  245. return columnHelper;
  246. }
  247. /**
  248. * Returns the name of this sheet
  249. *
  250. * @return the name of this sheet
  251. */
  252. @Override
  253. public String getSheetName() {
  254. return sheet.getName();
  255. }
  256. /**
  257. * Adds a merged region of cells on a sheet.
  258. *
  259. * @param region to merge
  260. * @return index of this region
  261. * @throws IllegalArgumentException if region contains fewer than 2 cells
  262. * @throws IllegalStateException if region intersects with a multi-cell array formula
  263. * @throws IllegalStateException if region intersects with an existing region on this sheet
  264. */
  265. @Override
  266. public int addMergedRegion(CellRangeAddress region) {
  267. return addMergedRegion(region, true);
  268. }
  269. /**
  270. * Adds a merged region of cells (hence those cells form one).
  271. * Skips validation. It is possible to create overlapping merged regions
  272. * or create a merged region that intersects a multi-cell array formula
  273. * with this formula, which may result in a corrupt workbook.
  274. *
  275. * To check for merged regions overlapping array formulas or other merged regions
  276. * after addMergedRegionUnsafe has been called, call {@link #validateMergedRegions()}, which runs in O(n^2) time.
  277. *
  278. * @param region to merge
  279. * @return index of this region
  280. * @throws IllegalArgumentException if region contains fewer than 2 cells
  281. */
  282. @Override
  283. public int addMergedRegionUnsafe(CellRangeAddress region) {
  284. return addMergedRegion(region, false);
  285. }
  286. /**
  287. * Adds a merged region of cells (hence those cells form one).
  288. * If validate is true, check to make sure adding the merged region to the sheet doesn't create a corrupt workbook
  289. * If validate is false, skips the expensive merged region checks, but may produce a corrupt workbook.
  290. *
  291. * @param region to merge
  292. * @param validate whether to validate merged region
  293. * @return 0-based index of this region
  294. * @throws IllegalArgumentException if region contains fewer than 2 cells (this check is inexpensive and is performed regardless of {@code validate})
  295. * @throws IllegalStateException if region intersects with a multi-cell array formula
  296. * @throws IllegalStateException if region intersects with an existing region on this sheet
  297. */
  298. private int addMergedRegion(CellRangeAddress region, boolean validate) {
  299. if (region.getNumberOfCells() < 2) {
  300. throw new IllegalArgumentException("Merged region " + region.formatAsString() + " must contain 2 or more cells");
  301. }
  302. region.validate(SpreadsheetVersion.EXCEL2007);
  303. if (validate) {
  304. // throw IllegalStateException if the argument CellRangeAddress intersects with
  305. // a multi-cell array formula defined in this sheet
  306. validateArrayFormulas(region);
  307. // Throw IllegalStateException if the argument CellRangeAddress intersects with
  308. // a merged region already in this sheet
  309. validateMergedRegions(region);
  310. }
  311. CTMergeCells ctMergeCells = worksheet.isSetMergeCells() ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
  312. CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
  313. ctMergeCell.setRef(region.formatAsString());
  314. long count = ctMergeCells.getCount();
  315. if (count == 0) {
  316. count=ctMergeCells.sizeOfMergeCellArray();
  317. } else {
  318. count++;
  319. }
  320. // also adjust the number of merged regions overall
  321. ctMergeCells.setCount(count);
  322. return Math.toIntExact(count-1);
  323. }
  324. /**
  325. * Verify that the candidate region does not intersect with an existing multi-cell array formula in this sheet
  326. *
  327. * @param region a region that is validated.
  328. * @throws IllegalStateException if candidate region intersects an existing array formula in this sheet
  329. */
  330. private void validateArrayFormulas(CellRangeAddress region) {
  331. // FIXME: this may be faster if it looped over array formulas directly rather than looping over each cell in
  332. // the region and searching if that cell belongs to an array formula
  333. int firstRow = region.getFirstRow();
  334. int firstColumn = region.getFirstColumn();
  335. int lastRow = region.getLastRow();
  336. int lastColumn = region.getLastColumn();
  337. // for each cell in sheet, if cell belongs to an array formula, check if merged region intersects array formula cells
  338. for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) {
  339. XSSFRow row = getRow(rowIn);
  340. if (row == null) {
  341. continue;
  342. }
  343. for (int colIn = firstColumn; colIn <= lastColumn; colIn++) {
  344. XSSFCell cell = row.getCell(colIn);
  345. if (cell == null) {
  346. continue;
  347. }
  348. if (cell.isPartOfArrayFormulaGroup()) {
  349. CellRangeAddress arrayRange = cell.getArrayFormulaRange();
  350. if (arrayRange.getNumberOfCells() > 1 && region.intersects(arrayRange)) {
  351. String msg = "The range " + region.formatAsString() + " intersects with a multi-cell array formula. " +
  352. "You cannot merge cells of an array.";
  353. throw new IllegalStateException(msg);
  354. }
  355. }
  356. }
  357. }
  358. }
  359. /**
  360. * Verify that none of the merged regions intersect a multi-cell array formula in this sheet
  361. *
  362. * @throws IllegalStateException if candidate region intersects an existing array formula in this sheet
  363. */
  364. private void checkForMergedRegionsIntersectingArrayFormulas() {
  365. for (CellRangeAddress region : getMergedRegions()) {
  366. validateArrayFormulas(region);
  367. }
  368. }
  369. /**
  370. * Verify that candidate region does not intersect with an existing merged region in this sheet
  371. *
  372. * @param candidateRegion the range of cells to verify
  373. * @throws IllegalStateException if candidate region intersects an existing merged region in this sheet (or candidateRegion is already merged in this sheet)
  374. */
  375. private void validateMergedRegions(CellRangeAddress candidateRegion) {
  376. for (final CellRangeAddress existingRegion : getMergedRegions()) {
  377. if (existingRegion.intersects(candidateRegion)) {
  378. throw new IllegalStateException("Cannot add merged region " + candidateRegion.formatAsString() +
  379. " to sheet because it overlaps with an existing merged region (" + existingRegion.formatAsString() + ").");
  380. }
  381. }
  382. }
  383. /**
  384. * Verify that no merged regions intersect another merged region in this sheet.
  385. *
  386. * @throws IllegalStateException if at least one region intersects with another merged region in this sheet
  387. */
  388. private void checkForIntersectingMergedRegions() {
  389. final List<CellRangeAddress> regions = getMergedRegions();
  390. final int size = regions.size();
  391. for (int i=0; i < size; i++) {
  392. final CellRangeAddress region = regions.get(i);
  393. for (final CellRangeAddress other : regions.subList(i+1, regions.size())) {
  394. if (region.intersects(other)) {
  395. String msg = "The range " + region.formatAsString() +
  396. " intersects with another merged region " +
  397. other.formatAsString() + " in this sheet";
  398. throw new IllegalStateException(msg);
  399. }
  400. }
  401. }
  402. }
  403. /**
  404. * Verify that merged regions do not intersect multi-cell array formulas and
  405. * no merged regions intersect another merged region in this sheet.
  406. *
  407. * @throws IllegalStateException if region intersects with a multi-cell array formula
  408. * @throws IllegalStateException if at least one region intersects with another merged region in this sheet
  409. */
  410. @Override
  411. public void validateMergedRegions() {
  412. checkForMergedRegionsIntersectingArrayFormulas();
  413. checkForIntersectingMergedRegions();
  414. }
  415. /**
  416. * Adjusts the column width to fit the contents.
  417. *
  418. * This process can be relatively slow on large sheets, so this should
  419. * normally only be called once per column, at the end of your
  420. * processing.
  421. *
  422. * @param column the column index
  423. */
  424. @Override
  425. public void autoSizeColumn(int column) {
  426. autoSizeColumn(column, false);
  427. }
  428. /**
  429. * Adjusts the column width to fit the contents.
  430. * <p>
  431. * This process can be relatively slow on large sheets, so this should
  432. * normally only be called once per column, at the end of your
  433. * processing.
  434. * </p>
  435. * You can specify whether the content of merged cells should be considered or ignored.
  436. * Default is to ignore merged cells.
  437. *
  438. * @param column the column index
  439. * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
  440. */
  441. @Override
  442. public void autoSizeColumn(int column, boolean useMergedCells) {
  443. double width = SheetUtil.getColumnWidth(this, column, useMergedCells);
  444. if (width != -1) {
  445. width *= 256;
  446. int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters
  447. if (width > maxColumnWidth) {
  448. width = maxColumnWidth;
  449. }
  450. setColumnWidth(column, Math.toIntExact(Math.round(width)));
  451. columnHelper.setColBestFit(column, true);
  452. }
  453. }
  454. /**
  455. * Return the sheet's existing drawing, or null if there isn't yet one.
  456. *
  457. * Use {@link #createDrawingPatriarch()} to get or create
  458. *
  459. * @return a SpreadsheetML drawing
  460. */
  461. @Override
  462. public XSSFDrawing getDrawingPatriarch() {
  463. CTDrawing ctDrawing = getCTDrawing();
  464. if (ctDrawing != null) {
  465. // Search the referenced drawing in the list of the sheet's relations
  466. for (RelationPart rp : getRelationParts()){
  467. POIXMLDocumentPart p = rp.getDocumentPart();
  468. if (p instanceof XSSFDrawing) {
  469. XSSFDrawing dr = (XSSFDrawing)p;
  470. String drId = rp.getRelationship().getId();
  471. if (drId.equals(ctDrawing.getId())){
  472. return dr;
  473. }
  474. }
  475. }
  476. LOG.atError().log("Can't find drawing with id={} in the list of the sheet's relationships", ctDrawing.getId());
  477. }
  478. return null;
  479. }
  480. /**
  481. * Create a new SpreadsheetML drawing. If this sheet already contains a drawing - return that.
  482. *
  483. * @return a SpreadsheetML drawing
  484. */
  485. @Override
  486. public XSSFDrawing createDrawingPatriarch() {
  487. XSSFDrawing existingDrawing = getDrawingPatriarch();
  488. if (existingDrawing != null) {
  489. return existingDrawing;
  490. }
  491. // Default drawingNumber = #drawings.size() + 1
  492. int drawingNumber = getPackagePart().getPackage().getPartsByContentType(XSSFRelation.DRAWINGS.getContentType()).size() + 1;
  493. drawingNumber = getNextPartNumber(XSSFRelation.DRAWINGS, drawingNumber);
  494. RelationPart rp = createRelationship(XSSFRelation.DRAWINGS, getWorkbook().getXssfFactory(), drawingNumber, false);
  495. XSSFDrawing drawing = rp.getDocumentPart();
  496. String relId = rp.getRelationship().getId();
  497. //add CT_Drawing element which indicates that this sheet contains drawing components built on the drawingML platform.
  498. //The relationship Id references the part containing the drawingML definitions.
  499. CTDrawing ctDrawing = worksheet.addNewDrawing();
  500. ctDrawing.setId(relId);
  501. // Return the newly created drawing
  502. return drawing;
  503. }
  504. /**
  505. * Get VML drawing for this sheet (aka 'legacy' drawing).
  506. *
  507. * @param autoCreate if true, then a new VML drawing part is created
  508. *
  509. * @return the VML drawing of {@code null} if the drawing was not found and autoCreate=false
  510. */
  511. @Override
  512. public XSSFVMLDrawing getVMLDrawing(boolean autoCreate) {
  513. if (xssfvmlDrawing == null) {
  514. XSSFVMLDrawing drawing = null;
  515. CTLegacyDrawing ctDrawing = getCTLegacyDrawing();
  516. if(ctDrawing == null) {
  517. if(autoCreate) {
  518. int drawingNumber = getNextPartNumber(XSSFRelation.VML_DRAWINGS,
  519. getPackagePart().getPackage().getPartsByContentType(XSSFRelation.VML_DRAWINGS.getContentType()).size());
  520. RelationPart rp = createRelationship(XSSFRelation.VML_DRAWINGS, getWorkbook().getXssfFactory(), drawingNumber, false);
  521. drawing = rp.getDocumentPart();
  522. String relId = rp.getRelationship().getId();
  523. //add CTLegacyDrawing element which indicates that this sheet contains drawing components built on the drawingML platform.
  524. //The relationship Id references the part containing the drawing definitions.
  525. ctDrawing = worksheet.addNewLegacyDrawing();
  526. ctDrawing.setId(relId);
  527. }
  528. } else {
  529. //search the referenced drawing in the list of the sheet's relations
  530. final String id = ctDrawing.getId();
  531. for (RelationPart rp : getRelationParts()){
  532. POIXMLDocumentPart p = rp.getDocumentPart();
  533. if(p instanceof XSSFVMLDrawing) {
  534. XSSFVMLDrawing dr = (XSSFVMLDrawing)p;
  535. String drId = rp.getRelationship().getId();
  536. if (drId.equals(id)) {
  537. drawing = dr;
  538. break;
  539. }
  540. // do not break here since drawing has not been found yet (see bug 52425)
  541. }
  542. }
  543. if(drawing == null){
  544. LOG.atError().log("Can't find VML drawing with id={} in the list of the sheet's relationships", id);
  545. }
  546. }
  547. xssfvmlDrawing = drawing;
  548. }
  549. return xssfvmlDrawing;
  550. }
  551. protected CTDrawing getCTDrawing() {
  552. return worksheet.getDrawing();
  553. }
  554. protected CTLegacyDrawing getCTLegacyDrawing() {
  555. return worksheet.getLegacyDrawing();
  556. }
  557. /**
  558. * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
  559. * @param colSplit Horizontal position of split.
  560. * @param rowSplit Vertical position of split.
  561. */
  562. @Override
  563. public void createFreezePane(int colSplit, int rowSplit) {
  564. createFreezePane( colSplit, rowSplit, colSplit, rowSplit );
  565. }
  566. /**
  567. * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
  568. *
  569. * <p>
  570. * If both colSplit and rowSplit are zero then the existing freeze pane is removed
  571. * </p>
  572. *
  573. * @param colSplit Horizontal position of split.
  574. * @param rowSplit Vertical position of split.
  575. * @param leftmostColumn Left column visible in right pane.
  576. * @param topRow Top row visible in bottom pane
  577. */
  578. @Override
  579. public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) {
  580. final boolean removeSplit = colSplit == 0 && rowSplit == 0;
  581. final CTSheetView ctView = getDefaultSheetView(!removeSplit);
  582. if (ctView != null) {
  583. ctView.setSelectionArray(null);
  584. }
  585. // If both colSplit and rowSplit are zero then the existing freeze pane is removed
  586. if (removeSplit) {
  587. if (ctView != null && ctView.isSetPane()) {
  588. ctView.unsetPane();
  589. }
  590. return;
  591. }
  592. assert(ctView != null);
  593. final CTPane pane = (ctView.isSetPane()) ? ctView.getPane() : ctView.addNewPane();
  594. assert(pane != null);
  595. if (colSplit > 0) {
  596. pane.setXSplit(colSplit);
  597. } else if (pane.isSetXSplit()) {
  598. pane.unsetXSplit();
  599. }
  600. if (rowSplit > 0) {
  601. pane.setYSplit(rowSplit);
  602. } else if(pane.isSetYSplit()) {
  603. pane.unsetYSplit();
  604. }
  605. STPane.Enum activePane = STPane.BOTTOM_RIGHT;
  606. int pRow = topRow, pCol = leftmostColumn;
  607. if (rowSplit == 0) {
  608. pRow = 0;
  609. activePane = STPane.TOP_RIGHT;
  610. } else if (colSplit == 0) {
  611. pCol = 0;
  612. activePane = STPane.BOTTOM_LEFT;
  613. }
  614. pane.setState(STPaneState.FROZEN);
  615. pane.setTopLeftCell(new CellReference(pRow, pCol).formatAsString());
  616. pane.setActivePane(activePane);
  617. ctView.addNewSelection().setPane(activePane);
  618. }
  619. /**
  620. * Create a new row within the sheet and return the high level representation
  621. *
  622. * Note: If a row already exists at this position, it is removed/overwritten and
  623. * any existing cell is removed!
  624. *
  625. * @param rownum row number
  626. * @return High level {@link XSSFRow} object representing a row in the sheet
  627. * @see #removeRow(Row)
  628. */
  629. @Override
  630. public XSSFRow createRow(int rownum) {
  631. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  632. //noinspection UnnecessaryBoxing
  633. final Integer rownumI = Integer.valueOf(rownum); // NOSONAR
  634. CTRow ctRow;
  635. XSSFRow prev = _rows.get(rownumI);
  636. if(prev != null){
  637. // the Cells in an existing row are invalidated on-purpose, in order to clean up correctly, we
  638. // need to call the remove, so things like ArrayFormulas and CalculationChain updates are done
  639. // correctly.
  640. // We remove the cell this way as the internal cell-list is changed by the remove call and
  641. // thus would cause ConcurrentModificationException otherwise
  642. while(prev.getFirstCellNum() != -1) {
  643. prev.removeCell(prev.getCell(prev.getFirstCellNum()));
  644. }
  645. ctRow = prev.getCTRow();
  646. ctRow.set(CTRow.Factory.newInstance());
  647. } else {
  648. if(_rows.isEmpty() || rownum > _rows.lastKey()) {
  649. // we can append the new row at the end
  650. ctRow = worksheet.getSheetData().addNewRow();
  651. } else {
  652. // get number of rows where row index < rownum
  653. // --> this tells us where our row should go
  654. int idx = _rows.headMap(rownumI).size();
  655. ctRow = worksheet.getSheetData().insertNewRow(idx);
  656. }
  657. }
  658. XSSFRow r = new XSSFRow(ctRow, this);
  659. r.setRowNum(rownum);
  660. _rows.put(rownumI, r);
  661. return r;
  662. }
  663. /**
  664. * Creates a split pane. Any existing freezepane or split pane is overwritten.
  665. * @param xSplitPos Horizontal position of split (in 1/20th of a point).
  666. * @param ySplitPos Vertical position of split (in 1/20th of a point).
  667. * @param topRow Top row visible in bottom pane
  668. * @param leftmostColumn Left column visible in right pane.
  669. * @param activePane Active pane. One of: PANE_LOWER_RIGHT,
  670. * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT (but there is a
  671. * <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=66173">bug</a>, so add 1)
  672. * @see #PANE_LOWER_LEFT
  673. * @see #PANE_LOWER_RIGHT
  674. * @see #PANE_UPPER_LEFT
  675. * @see #PANE_UPPER_RIGHT
  676. * @deprecated use {@link #createSplitPane(int, int, int, int, PaneType)}
  677. */
  678. @Override
  679. @Deprecated
  680. @Removal(version = "7.0.0")
  681. public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) {
  682. createFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow);
  683. if (xSplitPos > 0 || ySplitPos > 0) {
  684. final CTPane pane = getPane(true);
  685. pane.setState(STPaneState.SPLIT);
  686. pane.setActivePane(STPane.Enum.forInt(activePane));
  687. }
  688. }
  689. /**
  690. * Creates a split pane. Any existing freezepane or split pane is overwritten.
  691. * @param xSplitPos Horizontal position of split (in 1/20th of a point).
  692. * @param ySplitPos Vertical position of split (in 1/20th of a point).
  693. * @param topRow Top row visible in bottom pane
  694. * @param leftmostColumn Left column visible in right pane.
  695. * @param activePane Active pane.
  696. * @see PaneType
  697. * @since POI 5.2.3
  698. */
  699. @Override
  700. public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PaneType activePane) {
  701. createFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow);
  702. if (xSplitPos > 0 || ySplitPos > 0) {
  703. final CTPane pane = getPane(true);
  704. pane.setState(STPaneState.SPLIT);
  705. STPane.Enum stPaneEnum;
  706. switch (activePane) {
  707. case LOWER_RIGHT:
  708. stPaneEnum = STPane.BOTTOM_RIGHT;
  709. break;
  710. case UPPER_RIGHT:
  711. stPaneEnum = STPane.TOP_RIGHT;
  712. break;
  713. case LOWER_LEFT:
  714. stPaneEnum = STPane.BOTTOM_LEFT;
  715. break;
  716. case UPPER_LEFT:
  717. default:
  718. stPaneEnum = STPane.TOP_LEFT;
  719. break;
  720. }
  721. pane.setActivePane(stPaneEnum);
  722. }
  723. }
  724. /**
  725. * Return cell comment at row, column, if one exists. Otherwise returns null.
  726. *
  727. * @param address the location of the cell comment
  728. * @return the cell comment, if one exists. Otherwise, return null.
  729. */
  730. @Override
  731. public XSSFComment getCellComment(CellAddress address) {
  732. if (sheetComments == null) {
  733. return null;
  734. }
  735. return sheetComments.findCellComment(address);
  736. }
  737. /**
  738. * Returns all cell comments on this sheet.
  739. * @return A map of each Comment in the sheet, keyed on the cell address where
  740. * the comment is located.
  741. */
  742. @Override
  743. public Map<CellAddress, XSSFComment> getCellComments() {
  744. if (sheetComments == null) {
  745. return Collections.emptyMap();
  746. }
  747. // the cell comments in sheetComments.getCellComments() do not have the client anchors set
  748. Map<CellAddress, XSSFComment> map = new HashMap<>();
  749. for(Iterator<CellAddress> iter = sheetComments.getCellAddresses(); iter.hasNext(); ) {
  750. CellAddress address = iter.next();
  751. map.put(address, getCellComment(address));
  752. }
  753. return map;
  754. }
  755. /**
  756. * Get a Hyperlink in this sheet anchored at row, column
  757. *
  758. * @param row The row where the hyperlink is anchored
  759. * @param column The column where the hyperlink is anchored
  760. * @return hyperlink if there is a hyperlink anchored at row, column; otherwise returns null
  761. */
  762. @Override
  763. public XSSFHyperlink getHyperlink(int row, int column) {
  764. return getHyperlink(new CellAddress(row, column));
  765. }
  766. /**
  767. * Get a Hyperlink in this sheet located in a cell specified by {code addr}
  768. *
  769. * @param addr The address of the cell containing the hyperlink
  770. * @return hyperlink if there is a hyperlink anchored at {@code addr}; otherwise returns {@code null}
  771. * @since POI 3.15 beta 3
  772. */
  773. @Override
  774. public XSSFHyperlink getHyperlink(CellAddress addr) {
  775. for (XSSFHyperlink hyperlink : getHyperlinkList()) {
  776. if (addr.getRow() >= hyperlink.getFirstRow() && addr.getRow() <= hyperlink.getLastRow()
  777. && addr.getColumn() >= hyperlink.getFirstColumn() && addr.getColumn() <= hyperlink.getLastColumn()) {
  778. return hyperlink;
  779. }
  780. }
  781. return null;
  782. }
  783. /**
  784. * Get a list of Hyperlinks in this sheet
  785. *
  786. * @return Hyperlinks for the sheet
  787. */
  788. @Override
  789. public List<XSSFHyperlink> getHyperlinkList() {
  790. return Collections.unmodifiableList(hyperlinks);
  791. }
  792. private int[] getBreaks(CTPageBreak ctPageBreak) {
  793. CTBreak[] brkArray = ctPageBreak.getBrkArray();
  794. int[] breaks = new int[brkArray.length];
  795. for (int i = 0 ; i < brkArray.length ; i++) {
  796. breaks[i] = Math.toIntExact(brkArray[i].getId() - 1);
  797. }
  798. return breaks;
  799. }
  800. private void removeBreak(int index, CTPageBreak ctPageBreak) {
  801. int index1 = index + 1;
  802. CTBreak[] brkArray = ctPageBreak.getBrkArray();
  803. for (int i = 0 ; i < brkArray.length ; i++) {
  804. if (brkArray[i].getId() == index1) {
  805. ctPageBreak.removeBrk(i);
  806. // TODO: check if we can break here, i.e. if a page can have more than 1 break on the same id
  807. }
  808. }
  809. }
  810. /**
  811. * Vertical page break information used for print layout view, page layout view, drawing print breaks
  812. * in normal view, and for printing the worksheet.
  813. *
  814. * @return column indexes of all the vertical page breaks, never {@code null}
  815. */
  816. @Override
  817. public int[] getColumnBreaks() {
  818. return worksheet.isSetColBreaks() ? getBreaks(worksheet.getColBreaks()) : new int[0];
  819. }
  820. /**
  821. * Get the actual column width (in units of 1/256th of a character width )
  822. *
  823. * <p>
  824. * Note, the returned value is always greater that {@link #getDefaultColumnWidth()} because the latter does not include margins.
  825. * Actual column width measured as the number of characters of the maximum digit width of the
  826. * numbers 0, 1, 2, ..., 9 as rendered in the normal style's font. There are 4 pixels of margin
  827. * padding (two on each side), plus 1 pixel padding for the gridlines.
  828. * </p>
  829. *
  830. * @param columnIndex - the column to set (0-based)
  831. * @return width - the width in units of 1/256th of a character width
  832. */
  833. @Override
  834. public int getColumnWidth(int columnIndex) {
  835. CTCol col = columnHelper.getColumn(columnIndex, false);
  836. double width = col == null || !col.isSetWidth() ? getDefaultColumnWidth() : col.getWidth();
  837. return Math.toIntExact(Math.round(width*256));
  838. }
  839. /**
  840. * Get the actual column width in pixels
  841. *
  842. * <p>
  843. * Please note, that this method works correctly only for workbooks
  844. * with the default font size (Calibri 11pt for .xlsx).
  845. * </p>
  846. */
  847. @Override
  848. public float getColumnWidthInPixels(int columnIndex) {
  849. float widthIn256 = getColumnWidth(columnIndex);
  850. return (float)(widthIn256/256.0*Units.DEFAULT_CHARACTER_WIDTH);
  851. }
  852. /**
  853. * Get the default column width for the sheet (if the columns do not define their own width) in
  854. * characters.
  855. * <p>
  856. * Note, this value is different from {@link #getColumnWidth(int)}. The latter is always greater and includes
  857. * 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.
  858. * </p>
  859. * @return column width, default value is 8
  860. */
  861. @Override
  862. public int getDefaultColumnWidth() {
  863. CTSheetFormatPr pr = worksheet.getSheetFormatPr();
  864. return pr == null ? 8 : Math.toIntExact(pr.getBaseColWidth());
  865. }
  866. /**
  867. * Get the default row height for the sheet (if the rows do not define their own height) in
  868. * twips (1/20 of a point)
  869. *
  870. * @return default row height
  871. */
  872. @Override
  873. public short getDefaultRowHeight() {
  874. return (short)(getDefaultRowHeightInPoints() * Font.TWIPS_PER_POINT);
  875. }
  876. /**
  877. * Get the default row height for the sheet measured in point size (if the rows do not define their own height).
  878. *
  879. * @return default row height in points
  880. */
  881. @Override
  882. public float getDefaultRowHeightInPoints() {
  883. CTSheetFormatPr pr = worksheet.getSheetFormatPr();
  884. return (float)(pr == null ? 0 : pr.getDefaultRowHeight());
  885. }
  886. private CTSheetFormatPr getSheetTypeSheetFormatPr(final boolean createIfNotExists) {
  887. if (worksheet.isSetSheetFormatPr()) {
  888. return worksheet.getSheetFormatPr();
  889. }
  890. return createIfNotExists ? worksheet.addNewSheetFormatPr() : null;
  891. }
  892. /**
  893. * Returns the CellStyle that applies to the given
  894. * (0 based) column, or null if no style has been
  895. * set for that column
  896. */
  897. @Override
  898. public CellStyle getColumnStyle(int column) {
  899. int idx = columnHelper.getColDefaultStyle(column);
  900. return getWorkbook().getCellStyleAt(idx == -1 ? 0 : idx);
  901. }
  902. /**
  903. * Sets whether the worksheet is displayed from right to left instead of from left to right.
  904. *
  905. * @param value true for right to left, false otherwise.
  906. */
  907. @Override
  908. public void setRightToLeft(boolean value) {
  909. final CTSheetView dsv = getDefaultSheetView(true);
  910. assert(dsv != null);
  911. dsv.setRightToLeft(value);
  912. }
  913. /**
  914. * Whether the text is displayed in right-to-left mode in the window
  915. *
  916. * @return whether the text is displayed in right-to-left mode in the window
  917. */
  918. @Override
  919. public boolean isRightToLeft() {
  920. final CTSheetView dsv = getDefaultSheetView(false);
  921. return (dsv != null && dsv.getRightToLeft());
  922. }
  923. /**
  924. * Get whether to display the guts or not,
  925. * default value is true
  926. *
  927. * @return boolean - guts or no guts
  928. */
  929. @Override
  930. public boolean getDisplayGuts() {
  931. CTSheetPr sheetPr = getSheetTypeSheetPr();
  932. CTOutlinePr outlinePr = sheetPr.getOutlinePr() == null ? CTOutlinePr.Factory.newInstance() : sheetPr.getOutlinePr();
  933. return outlinePr.getShowOutlineSymbols();
  934. }
  935. /**
  936. * Set whether to display the guts or not
  937. *
  938. * @param value - guts or no guts
  939. */
  940. @Override
  941. public void setDisplayGuts(boolean value) {
  942. CTSheetPr sheetPr = getSheetTypeSheetPr();
  943. CTOutlinePr outlinePr = sheetPr.getOutlinePr() == null ? sheetPr.addNewOutlinePr() : sheetPr.getOutlinePr();
  944. outlinePr.setShowOutlineSymbols(value);
  945. }
  946. /**
  947. * Gets the flag indicating whether the window should show 0 (zero) in cells containing zero value.
  948. * When false, cells with zero value appear blank instead of showing the number zero.
  949. *
  950. * @return whether all zero values on the worksheet are displayed (defaults to true)
  951. */
  952. @Override
  953. public boolean isDisplayZeros(){
  954. final CTSheetView dsv = getDefaultSheetView(false);
  955. return (dsv == null) || dsv.getShowZeros();
  956. }
  957. /**
  958. * Set whether the window should show 0 (zero) in cells containing zero value.
  959. * When false, cells with zero value appear blank instead of showing the number zero.
  960. *
  961. * @param value whether to display or hide all zero values on the worksheet
  962. */
  963. @Override
  964. public void setDisplayZeros(boolean value){
  965. final CTSheetView view = getDefaultSheetView(true);
  966. assert(view != null);
  967. view.setShowZeros(value);
  968. }
  969. /**
  970. * Gets the first row on the sheet
  971. *
  972. * @return the number of the first logical row on the sheet, zero based
  973. */
  974. @Override
  975. public int getFirstRowNum() {
  976. return _rows.isEmpty() ? -1 : _rows.firstKey();
  977. }
  978. /**
  979. * Flag indicating whether the Fit to Page print option is enabled.
  980. *
  981. * @return {@code true}
  982. */
  983. @Override
  984. public boolean getFitToPage() {
  985. CTSheetPr sheetPr = getSheetTypeSheetPr();
  986. CTPageSetUpPr psSetup = (sheetPr == null || !sheetPr.isSetPageSetUpPr()) ?
  987. CTPageSetUpPr.Factory.newInstance() : sheetPr.getPageSetUpPr();
  988. return psSetup.getFitToPage();
  989. }
  990. private CTSheetPr getSheetTypeSheetPr() {
  991. if (worksheet.getSheetPr() == null) {
  992. worksheet.setSheetPr(CTSheetPr.Factory.newInstance());
  993. }
  994. return worksheet.getSheetPr();
  995. }
  996. private CTHeaderFooter getSheetTypeHeaderFooter() {
  997. if (worksheet.getHeaderFooter() == null) {
  998. worksheet.setHeaderFooter(CTHeaderFooter.Factory.newInstance());
  999. }
  1000. return worksheet.getHeaderFooter();
  1001. }
  1002. /**
  1003. * Returns the default footer for the sheet,
  1004. * creating one as needed.
  1005. * You may also want to look at
  1006. * {@link #getFirstFooter()},
  1007. * {@link #getOddFooter()} and
  1008. * {@link #getEvenFooter()}
  1009. */
  1010. @Override
  1011. public Footer getFooter() {
  1012. // The default footer is an odd footer
  1013. return getOddFooter();
  1014. }
  1015. /**
  1016. * Returns the default header for the sheet,
  1017. * creating one as needed.
  1018. * You may also want to look at
  1019. * {@link #getFirstHeader()},
  1020. * {@link #getOddHeader()} and
  1021. * {@link #getEvenHeader()}
  1022. */
  1023. @Override
  1024. public Header getHeader() {
  1025. // The default header is an odd header
  1026. return getOddHeader();
  1027. }
  1028. /**
  1029. * Returns the odd footer. Used on all pages unless
  1030. * other footers also present, when used on only
  1031. * odd pages.
  1032. */
  1033. public Footer getOddFooter() {
  1034. return new XSSFOddFooter(getSheetTypeHeaderFooter());
  1035. }
  1036. /**
  1037. * Returns the even footer. Not there by default, but
  1038. * when set, used on even pages.
  1039. */
  1040. public Footer getEvenFooter() {
  1041. return new XSSFEvenFooter(getSheetTypeHeaderFooter());
  1042. }
  1043. /**
  1044. * Returns the first page footer. Not there by
  1045. * default, but when set, used on the first page.
  1046. */
  1047. public Footer getFirstFooter() {
  1048. return new XSSFFirstFooter(getSheetTypeHeaderFooter());
  1049. }
  1050. /**
  1051. * Returns the odd header. Used on all pages unless
  1052. * other headers also present, when used on only
  1053. * odd pages.
  1054. */
  1055. public Header getOddHeader() {
  1056. return new XSSFOddHeader(getSheetTypeHeaderFooter());
  1057. }
  1058. /**
  1059. * Returns the even header. Not there by default, but
  1060. * when set, used on even pages.
  1061. */
  1062. public Header getEvenHeader() {
  1063. return new XSSFEvenHeader(getSheetTypeHeaderFooter());
  1064. }
  1065. /**
  1066. * Returns the first page header. Not there by
  1067. * default, but when set, used on the first page.
  1068. */
  1069. public Header getFirstHeader() {
  1070. return new XSSFFirstHeader(getSheetTypeHeaderFooter());
  1071. }
  1072. /**
  1073. * Determine whether printed output for this sheet will be horizontally centered.
  1074. */
  1075. @Override
  1076. public boolean getHorizontallyCenter() {
  1077. CTPrintOptions opts = worksheet.getPrintOptions();
  1078. return opts != null && opts.getHorizontalCentered();
  1079. }
  1080. @Override
  1081. public int getLastRowNum() {
  1082. // _rows.getLastKey() (O(logN)) or caching last row (O(1))?
  1083. // A test with 1_000_000 rows shows that querying getLastRowNum with lastKey() implementation takes ~40 ms,
  1084. // and ~1.2 ms with cached implementation. 40 ms is negligible compared to the time of evaluation a million
  1085. // cells, and the lastKey implementation is much more elegant and less error prone than caching.
  1086. return _rows.isEmpty() ? -1 : _rows.lastKey();
  1087. }
  1088. @Override
  1089. public short getLeftCol() {
  1090. String cellRef = worksheet.getSheetViews().getSheetViewArray(0).getTopLeftCell();
  1091. if(cellRef == null) {
  1092. return 0;
  1093. }
  1094. CellReference cellReference = new CellReference(cellRef);
  1095. return cellReference.getCol();
  1096. }
  1097. /**
  1098. * Gets the size of the margin in inches.
  1099. *
  1100. * @param margin which margin to get
  1101. * @return the size of the margin
  1102. * @see Sheet#LeftMargin
  1103. * @see Sheet#RightMargin
  1104. * @see Sheet#TopMargin
  1105. * @see Sheet#BottomMargin
  1106. * @see Sheet#HeaderMargin
  1107. * @see Sheet#FooterMargin
  1108. * @deprecated use {@link #getMargin(PageMargin)}
  1109. */
  1110. @Override
  1111. @Deprecated
  1112. @Removal(version = "7.0.0")
  1113. public double getMargin(short margin) {
  1114. return getMargin(PageMargin.getByShortValue(margin));
  1115. }
  1116. /**
  1117. * Gets the size of the margin in inches.
  1118. *
  1119. * @param margin which margin to get
  1120. * @return the size of the margin
  1121. * @since POI 5.2.3
  1122. */
  1123. @Override
  1124. public double getMargin(PageMargin margin) {
  1125. if (!worksheet.isSetPageMargins()) {
  1126. return 0;
  1127. }
  1128. CTPageMargins pageMargins = worksheet.getPageMargins();
  1129. switch (margin) {
  1130. case LEFT:
  1131. return pageMargins.getLeft();
  1132. case RIGHT:
  1133. return pageMargins.getRight();
  1134. case TOP:
  1135. return pageMargins.getTop();
  1136. case BOTTOM:
  1137. return pageMargins.getBottom();
  1138. case HEADER:
  1139. return pageMargins.getHeader();
  1140. case FOOTER:
  1141. return pageMargins.getFooter();
  1142. default :
  1143. throw new IllegalArgumentException("Unknown margin constant: " + margin);
  1144. }
  1145. }
  1146. /**
  1147. * Sets the size of the margin in inches.
  1148. *
  1149. * @param margin which margin to set
  1150. * @param size the size of the margin
  1151. * @see Sheet#LeftMargin
  1152. * @see Sheet#RightMargin
  1153. * @see Sheet#TopMargin
  1154. * @see Sheet#BottomMargin
  1155. * @see Sheet#HeaderMargin
  1156. * @see Sheet#FooterMargin
  1157. * @deprecated use {@link #setMargin(PageMargin, double)} instead
  1158. */
  1159. @Override
  1160. @Deprecated
  1161. @Removal(version = "7.0.0")
  1162. public void setMargin(short margin, double size) {
  1163. final PageMargin pageMargin = PageMargin.getByShortValue(margin);
  1164. if (pageMargin == null) {
  1165. throw new IllegalArgumentException( "Unknown margin constant: " + margin );
  1166. }
  1167. setMargin(pageMargin, size);
  1168. }
  1169. /**
  1170. * Sets the size of the margin in inches.
  1171. *
  1172. * @param margin which margin to set
  1173. * @param size the size of the margin
  1174. * @since POI 5.2.3
  1175. */
  1176. @Override
  1177. public void setMargin(PageMargin margin, double size) {
  1178. CTPageMargins pageMargins = worksheet.isSetPageMargins() ?
  1179. worksheet.getPageMargins() : worksheet.addNewPageMargins();
  1180. switch (margin) {
  1181. case LEFT:
  1182. pageMargins.setLeft(size);
  1183. break;
  1184. case RIGHT:
  1185. pageMargins.setRight(size);
  1186. break;
  1187. case TOP:
  1188. pageMargins.setTop(size);
  1189. break;
  1190. case BOTTOM:
  1191. pageMargins.setBottom(size);
  1192. break;
  1193. case HEADER:
  1194. pageMargins.setHeader(size);
  1195. break;
  1196. case FOOTER:
  1197. pageMargins.setFooter(size);
  1198. break;
  1199. default:
  1200. throw new IllegalArgumentException( "Unknown margin constant: " + margin );
  1201. }
  1202. }
  1203. /**
  1204. * Returns the merged region at the specified index. If you want multiple
  1205. * regions, it is faster to call {@link #getMergedRegions()} than to call
  1206. * this each time.
  1207. *
  1208. * @return the merged region at the specified index
  1209. */
  1210. @Override
  1211. public CellRangeAddress getMergedRegion(int index) {
  1212. CTMergeCells ctMergeCells = worksheet.getMergeCells();
  1213. if(ctMergeCells == null) {
  1214. throw new IllegalStateException("This worksheet does not contain merged regions");
  1215. }
  1216. CTMergeCell ctMergeCell = ctMergeCells.getMergeCellArray(index);
  1217. String ref = ctMergeCell.getRef();
  1218. return CellRangeAddress.valueOf(ref);
  1219. }
  1220. /**
  1221. * Returns the list of merged regions. If you want multiple regions, this is
  1222. * faster than calling {@link #getMergedRegion(int)} each time.
  1223. *
  1224. * @return the list of merged regions
  1225. */
  1226. @Override
  1227. public List<CellRangeAddress> getMergedRegions() {
  1228. List<CellRangeAddress> addresses = new ArrayList<>();
  1229. CTMergeCells ctMergeCells = worksheet.getMergeCells();
  1230. if(ctMergeCells == null) {
  1231. return addresses;
  1232. }
  1233. for(CTMergeCell ctMergeCell : ctMergeCells.getMergeCellArray()) {
  1234. String ref = ctMergeCell.getRef();
  1235. addresses.add(CellRangeAddress.valueOf(ref));
  1236. }
  1237. return addresses;
  1238. }
  1239. /**
  1240. * Returns the number of merged regions defined in this worksheet
  1241. *
  1242. * @return number of merged regions in this worksheet
  1243. */
  1244. @Override
  1245. public int getNumMergedRegions() {
  1246. CTMergeCells ctMergeCells = worksheet.getMergeCells();
  1247. return ctMergeCells == null ? 0 : ctMergeCells.sizeOfMergeCellArray();
  1248. }
  1249. public int getNumHyperlinks() {
  1250. return hyperlinks.size();
  1251. }
  1252. /**
  1253. * Returns the information regarding the currently configured pane (split or freeze).
  1254. *
  1255. * @return null if no pane configured, or the pane information.
  1256. */
  1257. @Override
  1258. public PaneInformation getPaneInformation() {
  1259. final CTPane pane = getPane(false);
  1260. // no pane configured
  1261. if(pane == null) {
  1262. return null;
  1263. }
  1264. short row = 0, col = 0;
  1265. if (pane.isSetTopLeftCell()) {
  1266. final CellReference cellRef = new CellReference(pane.getTopLeftCell());
  1267. row = (short)cellRef.getRow();
  1268. col = cellRef.getCol();
  1269. }
  1270. final short x = (short)pane.getXSplit();
  1271. final short y = (short)pane.getYSplit();
  1272. final byte active = (byte)(pane.getActivePane().intValue() - 1);
  1273. final boolean frozen = pane.getState() == STPaneState.FROZEN;
  1274. return new PaneInformation(x, y, row, col, active, frozen);
  1275. }
  1276. /**
  1277. * Returns the number of physically defined rows (NOT the number of rows in the sheet)
  1278. *
  1279. * @return the number of physically defined rows
  1280. */
  1281. @Override
  1282. public int getPhysicalNumberOfRows() {
  1283. return _rows.size();
  1284. }
  1285. /**
  1286. * Gets the print setup object.
  1287. *
  1288. * @return The user model for the print setup object.
  1289. */
  1290. @Override
  1291. public XSSFPrintSetup getPrintSetup() {
  1292. return new XSSFPrintSetup(worksheet);
  1293. }
  1294. /**
  1295. * Answer whether protection is enabled or disabled
  1296. *
  1297. * @return true =&gt; protection enabled; false =&gt; protection disabled
  1298. */
  1299. @Override
  1300. public boolean getProtect() {
  1301. return isSheetLocked();
  1302. }
  1303. /**
  1304. * Enables sheet protection and sets the password for the sheet.
  1305. * Also sets some attributes on the {@link CTSheetProtection} that correspond to
  1306. * the default values used by Excel
  1307. *
  1308. * @param password to set for protection. Pass {@code null} to remove protection
  1309. */
  1310. @Override
  1311. public void protectSheet(String password) {
  1312. if (password != null) {
  1313. CTSheetProtection sheetProtection = safeGetProtectionField();
  1314. setSheetPassword(password, null); // defaults to xor password
  1315. sheetProtection.setSheet(true);
  1316. sheetProtection.setScenarios(true);
  1317. sheetProtection.setObjects(true);
  1318. } else {
  1319. worksheet.unsetSheetProtection();
  1320. }
  1321. }
  1322. /**
  1323. * Sets the sheet password.
  1324. *
  1325. * @param password if null, the password will be removed
  1326. * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier)
  1327. * otherwise the given algorithm is used for calculating the hash password (Excel 2013)
  1328. */
  1329. public void setSheetPassword(String password, HashAlgorithm hashAlgo) {
  1330. if (password == null && !isSheetProtectionEnabled()) {
  1331. return;
  1332. }
  1333. setPassword(safeGetProtectionField(), password, hashAlgo, null);
  1334. }
  1335. /**
  1336. * Validate the password against the stored hash, the hashing method will be determined
  1337. * by the existing password attributes
  1338. * @return true, if the hashes match (... though original password may differ ...)
  1339. */
  1340. public boolean validateSheetPassword(String password) {
  1341. if (!isSheetProtectionEnabled()) {
  1342. return (password == null);
  1343. }
  1344. return validatePassword(safeGetProtectionField(), password, null);
  1345. }
  1346. /**
  1347. * Returns the logical row ( 0-based). If you ask for a row that is not
  1348. * defined you get a null. This is to say row 4 represents the fifth row on a sheet.
  1349. *
  1350. * @param rownum row to get
  1351. * @return {@code XSSFRow} representing the rownumber or {@code null} if its not defined on the sheet
  1352. */
  1353. @Override
  1354. public XSSFRow getRow(int rownum) {
  1355. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  1356. //noinspection UnnecessaryBoxing
  1357. final Integer rownumI = Integer.valueOf(rownum); // NOSONAR
  1358. return _rows.get(rownumI);
  1359. }
  1360. /**
  1361. * returns all rows between startRow and endRow, inclusive.
  1362. * Rows between startRow and endRow that haven't been created are not included
  1363. * in result unless createRowIfMissing is true
  1364. *
  1365. * @param startRowNum the first row number in this sheet to return
  1366. * @param endRowNum the last row number in this sheet to return
  1367. * @param createRowIfMissing If missing rows should be created.
  1368. * @return All rows between startRow and endRow, inclusive. If createRowIfMissing is false,
  1369. * only previously existing rows are returned, otherwise empty rows are added as necessary
  1370. * @throws IllegalArgumentException if startRowNum and endRowNum are not in ascending order
  1371. */
  1372. private List<XSSFRow> getRows(int startRowNum, int endRowNum, boolean createRowIfMissing) {
  1373. if (startRowNum > endRowNum) {
  1374. throw new IllegalArgumentException("getRows: startRowNum must be less than or equal to endRowNum");
  1375. }
  1376. final List<XSSFRow> rows = new ArrayList<>();
  1377. if (createRowIfMissing) {
  1378. for (int i = startRowNum; i <= endRowNum; i++) {
  1379. XSSFRow row = getRow(i);
  1380. if (row == null) {
  1381. row = createRow(i);
  1382. }
  1383. rows.add(row);
  1384. }
  1385. }
  1386. else {
  1387. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  1388. //noinspection UnnecessaryBoxing
  1389. final Integer startI = Integer.valueOf(startRowNum); // NOSONAR
  1390. //noinspection UnnecessaryBoxing
  1391. final Integer endI = Integer.valueOf(endRowNum+1); // NOSONAR
  1392. final Collection<XSSFRow> inclusive = _rows.subMap(startI, endI).values();
  1393. rows.addAll(inclusive);
  1394. }
  1395. return rows;
  1396. }
  1397. /**
  1398. * Horizontal page break information used for print layout view, page layout view, drawing print breaks in normal
  1399. * view, and for printing the worksheet.
  1400. *
  1401. * @return row indexes of all the horizontal page breaks, never {@code null}
  1402. */
  1403. @Override
  1404. public int[] getRowBreaks() {
  1405. return worksheet.isSetRowBreaks() ? getBreaks(worksheet.getRowBreaks()) : new int[0];
  1406. }
  1407. /**
  1408. * Flag indicating whether summary rows appear below detail in an outline, when applying an outline.
  1409. *
  1410. * <p>
  1411. * When true a summary row is inserted below the detailed data being summarized and a
  1412. * new outline level is established on that row.
  1413. * </p>
  1414. * <p>
  1415. * When false a summary row is inserted above the detailed data being summarized and a new outline level
  1416. * is established on that row.
  1417. * </p>
  1418. * @return {@code true} if row summaries appear below detail in the outline
  1419. */
  1420. @Override
  1421. public boolean getRowSumsBelow() {
  1422. CTSheetPr sheetPr = worksheet.getSheetPr();
  1423. CTOutlinePr outlinePr = (sheetPr != null && sheetPr.isSetOutlinePr())
  1424. ? sheetPr.getOutlinePr() : null;
  1425. return outlinePr == null || outlinePr.getSummaryBelow();
  1426. }
  1427. /**
  1428. * Flag indicating whether summary rows appear below detail in an outline, when applying an outline.
  1429. *
  1430. * <p>
  1431. * When true a summary row is inserted below the detailed data being summarized and a
  1432. * new outline level is established on that row.
  1433. * </p>
  1434. * <p>
  1435. * When false a summary row is inserted above the detailed data being summarized and a new outline level
  1436. * is established on that row.
  1437. * </p>
  1438. * @param value {@code true} if row summaries appear below detail in the outline
  1439. */
  1440. @Override
  1441. public void setRowSumsBelow(boolean value) {
  1442. ensureOutlinePr().setSummaryBelow(value);
  1443. }
  1444. /**
  1445. * Flag indicating whether summary columns appear to the right of detail in an outline, when applying an outline.
  1446. *
  1447. * <p>
  1448. * When true a summary column is inserted to the right of the detailed data being summarized
  1449. * and a new outline level is established on that column.
  1450. * </p>
  1451. * <p>
  1452. * When false a summary column is inserted to the left of the detailed data being
  1453. * summarized and a new outline level is established on that column.
  1454. * </p>
  1455. * @return {@code true} if col summaries appear right of the detail in the outline
  1456. */
  1457. @Override
  1458. public boolean getRowSumsRight() {
  1459. CTSheetPr sheetPr = worksheet.getSheetPr();
  1460. CTOutlinePr outlinePr = (sheetPr != null && sheetPr.isSetOutlinePr())
  1461. ? sheetPr.getOutlinePr() : CTOutlinePr.Factory.newInstance();
  1462. return outlinePr.getSummaryRight();
  1463. }
  1464. /**
  1465. * Flag indicating whether summary columns appear to the right of detail in an outline, when applying an outline.
  1466. *
  1467. * <p>
  1468. * When true a summary column is inserted to the right of the detailed data being summarized
  1469. * and a new outline level is established on that column.
  1470. * </p>
  1471. * <p>
  1472. * When false a summary column is inserted to the left of the detailed data being
  1473. * summarized and a new outline level is established on that column.
  1474. * </p>
  1475. * @param value {@code true} if col summaries appear right of the detail in the outline
  1476. */
  1477. @Override
  1478. public void setRowSumsRight(boolean value) {
  1479. ensureOutlinePr().setSummaryRight(value);
  1480. }
  1481. /**
  1482. * Ensure CTWorksheet.CTSheetPr.CTOutlinePr
  1483. */
  1484. private CTOutlinePr ensureOutlinePr(){
  1485. CTSheetPr sheetPr = worksheet.isSetSheetPr() ? worksheet.getSheetPr() : worksheet.addNewSheetPr();
  1486. return sheetPr.isSetOutlinePr() ? sheetPr.getOutlinePr() : sheetPr.addNewOutlinePr();
  1487. }
  1488. /**
  1489. * A flag indicating whether scenarios are locked when the sheet is protected.
  1490. *
  1491. * @return true =&gt; protection enabled; false =&gt; protection disabled
  1492. */
  1493. @Override
  1494. public boolean getScenarioProtect() {
  1495. return worksheet.isSetSheetProtection() && worksheet.getSheetProtection().getScenarios();
  1496. }
  1497. /**
  1498. * The top row in the visible view when the sheet is
  1499. * first viewed after opening it in a viewer
  1500. *
  1501. * @return integer indicating the rownum (0 based) of the top row
  1502. */
  1503. @Override
  1504. public short getTopRow() {
  1505. final CTSheetView dsv = getDefaultSheetView(false);
  1506. final String cellRef = (dsv == null) ? null : dsv.getTopLeftCell();
  1507. if(cellRef == null) {
  1508. return 0;
  1509. }
  1510. return (short) new CellReference(cellRef).getRow();
  1511. }
  1512. /**
  1513. * Determine whether printed output for this sheet will be vertically centered.
  1514. *
  1515. * @return whether printed output for this sheet will be vertically centered.
  1516. */
  1517. @Override
  1518. public boolean getVerticallyCenter() {
  1519. CTPrintOptions opts = worksheet.getPrintOptions();
  1520. return opts != null && opts.getVerticalCentered();
  1521. }
  1522. /**
  1523. * Group between (0 based) columns
  1524. */
  1525. @Override
  1526. public void groupColumn(int fromColumn, int toColumn) {
  1527. groupColumn1Based(fromColumn+1, toColumn+1);
  1528. }
  1529. private void groupColumn1Based(int fromColumn, int toColumn) {
  1530. CTCols ctCols=worksheet.getColsArray(0);
  1531. CTCol ctCol=CTCol.Factory.newInstance();
  1532. // copy attributes, as they might be removed by merging with the new column
  1533. // TODO: check if this fix is really necessary or if the sweeping algorithm
  1534. // in addCleanColIntoCols needs to be adapted ...
  1535. CTCol fixCol_before = this.columnHelper.getColumn1Based(toColumn, false);
  1536. if (fixCol_before != null) {
  1537. fixCol_before = (CTCol)fixCol_before.copy();
  1538. }
  1539. ctCol.setMin(fromColumn);
  1540. ctCol.setMax(toColumn);
  1541. this.columnHelper.addCleanColIntoCols(ctCols, ctCol);
  1542. CTCol fixCol_after = this.columnHelper.getColumn1Based(toColumn, false);
  1543. if (fixCol_before != null && fixCol_after != null) {
  1544. this.columnHelper.setColumnAttributes(fixCol_before, fixCol_after);
  1545. }
  1546. int maxLevelCol = -1;
  1547. for(int index = fromColumn; index <= toColumn; index++){
  1548. CTCol col = columnHelper.getColumn1Based(index, false);
  1549. //col must exist
  1550. final short outlineLevel = col.getOutlineLevel();
  1551. final int newOutlineLevel = outlineLevel + 1;
  1552. col.setOutlineLevel((short) newOutlineLevel);
  1553. maxLevelCol = Math.max(maxLevelCol, newOutlineLevel);
  1554. index = Math.toIntExact(col.getMax());
  1555. }
  1556. worksheet.setColsArray(0, ctCols);
  1557. increaseSheetFormatPrOutlineLevelColIfNecessary((short) Math.min(Short.MAX_VALUE, maxLevelCol));
  1558. }
  1559. /**
  1560. * Do not leave the width attribute undefined (see #52186).
  1561. */
  1562. private void setColWidthAttribute(CTCols ctCols) {
  1563. for (CTCol col : ctCols.getColArray()) {
  1564. if (!col.isSetWidth()) {
  1565. col.setWidth(getDefaultColumnWidth());
  1566. col.setCustomWidth(false);
  1567. }
  1568. }
  1569. }
  1570. /**
  1571. * Tie a range of cell together so that they can be collapsed or expanded
  1572. *
  1573. * @param fromRow start row (0-based)
  1574. * @param toRow end row (0-based)
  1575. */
  1576. @Override
  1577. public void groupRow(int fromRow, int toRow) {
  1578. int maxOutlineLevel = -1;
  1579. for (int i = fromRow; i <= toRow; i++) {
  1580. XSSFRow xrow = getRow(i);
  1581. if (xrow == null) {
  1582. xrow = createRow(i);
  1583. }
  1584. CTRow ctrow = xrow.getCTRow();
  1585. final short outlineLevel = ctrow.getOutlineLevel();
  1586. final int newOutlineLevel = outlineLevel + 1;
  1587. maxOutlineLevel = Math.max(maxOutlineLevel, newOutlineLevel);
  1588. ctrow.setOutlineLevel((short) newOutlineLevel);
  1589. }
  1590. increaseSheetFormatPrOutlineLevelRowIfNecessary((short) Math.min(Short.MAX_VALUE, maxOutlineLevel));
  1591. }
  1592. private short getMaxOutlineLevelRows(){
  1593. int outlineLevel = 0;
  1594. for (XSSFRow xrow : _rows.values()) {
  1595. outlineLevel = Math.max(outlineLevel, xrow.getCTRow().getOutlineLevel());
  1596. }
  1597. return (short) outlineLevel;
  1598. }
  1599. private short getMaxOutlineLevelCols() {
  1600. CTCols ctCols = worksheet.getColsArray(0);
  1601. int outlineLevel = 0;
  1602. for (CTCol col : ctCols.getColArray()) {
  1603. outlineLevel = Math.max(outlineLevel, col.getOutlineLevel());
  1604. }
  1605. return (short) outlineLevel;
  1606. }
  1607. /**
  1608. * Determines if there is a page break at the indicated column
  1609. */
  1610. @Override
  1611. public boolean isColumnBroken(int column) {
  1612. for (int colBreak : getColumnBreaks()) {
  1613. if (colBreak == column) {
  1614. return true;
  1615. }
  1616. }
  1617. return false;
  1618. }
  1619. /**
  1620. * Get the hidden state for a given column.
  1621. *
  1622. * @param columnIndex - the column to set (0-based)
  1623. * @return hidden - {@code false} if the column is visible
  1624. */
  1625. @Override
  1626. public boolean isColumnHidden(int columnIndex) {
  1627. CTCol col = columnHelper.getColumn(columnIndex, false);
  1628. return col != null && col.getHidden();
  1629. }
  1630. /**
  1631. * Gets the flag indicating whether this sheet should display formulas.
  1632. *
  1633. * @return {@code true} if this sheet should display formulas.
  1634. */
  1635. @Override
  1636. public boolean isDisplayFormulas() {
  1637. final CTSheetView dsv = getDefaultSheetView(false);
  1638. return dsv != null && dsv.getShowFormulas();
  1639. }
  1640. /**
  1641. * Gets the flag indicating whether this sheet displays the lines
  1642. * between rows and columns to make editing and reading easier.
  1643. *
  1644. * @return {@code true} (default) if this sheet displays gridlines.
  1645. * @see #isPrintGridlines() to check if printing of gridlines is turned on or off
  1646. */
  1647. @Override
  1648. public boolean isDisplayGridlines() {
  1649. final CTSheetView dsv = getDefaultSheetView(false);
  1650. return (dsv == null) || dsv.getShowGridLines();
  1651. }
  1652. /**
  1653. * Sets the flag indicating whether this sheet should display the lines
  1654. * between rows and columns to make editing and reading easier.
  1655. * To turn printing of gridlines use {@link #setPrintGridlines(boolean)}
  1656. *
  1657. *
  1658. * @param show {@code true} if this sheet should display gridlines.
  1659. * @see #setPrintGridlines(boolean)
  1660. */
  1661. @Override
  1662. public void setDisplayGridlines(boolean show) {
  1663. final CTSheetView dsv = getDefaultSheetView(true);
  1664. assert(dsv != null);
  1665. dsv.setShowGridLines(show);
  1666. }
  1667. /**
  1668. * Gets the flag indicating whether this sheet should display row and column headings.
  1669. * <p>
  1670. * Row heading are the row numbers to the side of the sheet
  1671. * </p>
  1672. * <p>
  1673. * Column heading are the letters or numbers that appear above the columns of the sheet
  1674. * </p>
  1675. *
  1676. * @return {@code true} (default) if this sheet should display row and column headings.
  1677. */
  1678. @Override
  1679. public boolean isDisplayRowColHeadings() {
  1680. final CTSheetView dsv = getDefaultSheetView(false);
  1681. return (dsv == null) || dsv.getShowRowColHeaders();
  1682. }
  1683. /**
  1684. * Sets the flag indicating whether this sheet should display row and column headings.
  1685. * <p>
  1686. * Row heading are the row numbers to the side of the sheet
  1687. * </p>
  1688. * <p>
  1689. * Column heading are the letters or numbers that appear above the columns of the sheet
  1690. * </p>
  1691. *
  1692. * @param show {@code true} if this sheet should display row and column headings.
  1693. */
  1694. @Override
  1695. public void setDisplayRowColHeadings(boolean show) {
  1696. final CTSheetView dsv = getDefaultSheetView(true);
  1697. assert(dsv != null);
  1698. dsv.setShowRowColHeaders(show);
  1699. }
  1700. /**
  1701. * Returns whether gridlines are printed.
  1702. *
  1703. * @return whether gridlines are printed
  1704. */
  1705. @Override
  1706. public boolean isPrintGridlines() {
  1707. CTPrintOptions opts = worksheet.getPrintOptions();
  1708. return opts != null && opts.getGridLines();
  1709. }
  1710. /**
  1711. * Turns on or off the printing of gridlines.
  1712. *
  1713. * @param value boolean to turn on or off the printing of gridlines
  1714. */
  1715. @Override
  1716. public void setPrintGridlines(boolean value) {
  1717. CTPrintOptions opts = worksheet.isSetPrintOptions() ?
  1718. worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
  1719. opts.setGridLines(value);
  1720. }
  1721. /**
  1722. * Returns whether row and column headings are printed.
  1723. *
  1724. * @return whether row and column headings are printed
  1725. */
  1726. @Override
  1727. public boolean isPrintRowAndColumnHeadings() {
  1728. CTPrintOptions opts = worksheet.getPrintOptions();
  1729. return opts != null && opts.getHeadings();
  1730. }
  1731. /**
  1732. * Turns on or off the printing of row and column headings.
  1733. *
  1734. * @param value boolean to turn on or off the printing of row and column headings
  1735. */
  1736. @Override
  1737. public void setPrintRowAndColumnHeadings(boolean value) {
  1738. CTPrintOptions opts = worksheet.isSetPrintOptions() ?
  1739. worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
  1740. opts.setHeadings(value);
  1741. }
  1742. /**
  1743. * Tests if there is a page break at the indicated row
  1744. *
  1745. * @param row index of the row to test
  1746. * @return {@code true} if there is a page break at the indicated row
  1747. */
  1748. @Override
  1749. public boolean isRowBroken(int row) {
  1750. for (int rowBreak : getRowBreaks()) {
  1751. if (rowBreak == row) {
  1752. return true;
  1753. }
  1754. }
  1755. return false;
  1756. }
  1757. private void setBreak(int id, CTPageBreak ctPgBreak, int lastIndex) {
  1758. CTBreak brk = ctPgBreak.addNewBrk();
  1759. // this is id of the element which is 1-based: <row r="1" ... >
  1760. brk.setId(id + 1L);
  1761. brk.setMan(true);
  1762. // end column of the break
  1763. brk.setMax(lastIndex);
  1764. int nPageBreaks = ctPgBreak.sizeOfBrkArray();
  1765. ctPgBreak.setCount(nPageBreaks);
  1766. ctPgBreak.setManualBreakCount(nPageBreaks);
  1767. }
  1768. /**
  1769. * Sets a page break at the indicated row
  1770. * Breaks occur above the specified row and left of the specified column inclusive.
  1771. *
  1772. * For example, {@code sheet.setColumnBreak(2);} breaks the sheet into two parts
  1773. * with columns A,B,C in the first and D,E,... in the second. Similar, {@code sheet.setRowBreak(2);}
  1774. * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part
  1775. * and rows starting with rownum=4 in the second.
  1776. *
  1777. * @param row the row to break, inclusive
  1778. */
  1779. @Override
  1780. public void setRowBreak(int row) {
  1781. if (!isRowBroken(row)) {
  1782. CTPageBreak pgBreak = worksheet.isSetRowBreaks() ? worksheet.getRowBreaks() : worksheet.addNewRowBreaks();
  1783. setBreak(row, pgBreak, SpreadsheetVersion.EXCEL2007.getLastColumnIndex());
  1784. }
  1785. }
  1786. /**
  1787. * Removes a page break at the indicated column
  1788. */
  1789. @Override
  1790. public void removeColumnBreak(int column) {
  1791. if (worksheet.isSetColBreaks()) {
  1792. removeBreak(column, worksheet.getColBreaks());
  1793. } // else no breaks
  1794. }
  1795. /**
  1796. * Removes a merged region of cells (hence letting them free)
  1797. *
  1798. * @param index of the region to unmerge
  1799. */
  1800. @Override
  1801. public void removeMergedRegion(int index) {
  1802. if (!worksheet.isSetMergeCells()) {
  1803. return;
  1804. }
  1805. CTMergeCells ctMergeCells = worksheet.getMergeCells();
  1806. int size = ctMergeCells.sizeOfMergeCellArray();
  1807. assert(0 <= index && index < size);
  1808. if (size > 1) {
  1809. ctMergeCells.removeMergeCell(index);
  1810. } else {
  1811. worksheet.unsetMergeCells();
  1812. }
  1813. }
  1814. /**
  1815. * Removes a number of merged regions of cells (hence letting them free)
  1816. *
  1817. * This method can be used to bulk-remove merged regions in a way
  1818. * much faster than calling removeMergedRegion() for every single
  1819. * merged region.
  1820. *
  1821. * @param indices A set of the regions to unmerge
  1822. */
  1823. @Override
  1824. public void removeMergedRegions(Collection<Integer> indices) {
  1825. if (!worksheet.isSetMergeCells()) {
  1826. return;
  1827. }
  1828. CTMergeCells ctMergeCells = worksheet.getMergeCells();
  1829. List<CTMergeCell> newMergeCells = new ArrayList<>(ctMergeCells.sizeOfMergeCellArray());
  1830. int idx = 0;
  1831. for (CTMergeCell mc : ctMergeCells.getMergeCellArray()) {
  1832. if (!indices.contains(idx++)) {
  1833. newMergeCells.add(mc);
  1834. }
  1835. }
  1836. if (newMergeCells.isEmpty()) {
  1837. worksheet.unsetMergeCells();
  1838. } else {
  1839. CTMergeCell[] newMergeCellsArray = new CTMergeCell[newMergeCells.size()];
  1840. ctMergeCells.setMergeCellArray(newMergeCells.toArray(newMergeCellsArray));
  1841. }
  1842. }
  1843. /**
  1844. * Remove a row from this sheet. All cells contained in the row are removed as well
  1845. *
  1846. * @param row the row to remove.
  1847. */
  1848. @Override
  1849. public void removeRow(Row row) {
  1850. if (row.getSheet() != this) {
  1851. throw new IllegalArgumentException("Specified row does not belong to this sheet");
  1852. }
  1853. // collect cells into a temporary array to avoid ConcurrentModificationException
  1854. ArrayList<XSSFCell> cellsToDelete = new ArrayList<>();
  1855. for (Cell cell : row) {
  1856. cellsToDelete.add((XSSFCell)cell);
  1857. }
  1858. for (XSSFCell cell : cellsToDelete) {
  1859. row.removeCell(cell);
  1860. }
  1861. final int rowNum = row.getRowNum();
  1862. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  1863. //noinspection UnnecessaryBoxing
  1864. final Integer rowNumI = Integer.valueOf(rowNum); // NOSONAR
  1865. // this is not the physical row number!
  1866. final int idx = _rows.headMap(rowNumI).size();
  1867. _rows.remove(rowNumI);
  1868. worksheet.getSheetData().removeRow(idx);
  1869. // also remove any comment located in that row
  1870. if(sheetComments != null) {
  1871. for (CellAddress ref : getCellComments().keySet()) {
  1872. if (ref.getRow() == rowNum) {
  1873. sheetComments.removeComment(ref);
  1874. }
  1875. }
  1876. }
  1877. }
  1878. /**
  1879. * Removes the page break at the indicated row
  1880. */
  1881. @Override
  1882. public void removeRowBreak(int row) {
  1883. if (worksheet.isSetRowBreaks()) {
  1884. removeBreak(row, worksheet.getRowBreaks());
  1885. } // else no breaks
  1886. }
  1887. /**
  1888. * Control if Excel should be asked to recalculate all formulas on this sheet
  1889. * when the workbook is opened.
  1890. *
  1891. * <p>
  1892. * Calculating the formula values with {@link FormulaEvaluator} is the
  1893. * recommended solution, but this may be used for certain cases where
  1894. * evaluation in POI is not possible.
  1895. * </p>
  1896. *
  1897. * <p>
  1898. * It is recommended to force recalcuation of formulas on workbook level using
  1899. * {@link Workbook#setForceFormulaRecalculation(boolean)}
  1900. * to ensure that all cross-worksheet formuals and external dependencies are updated.
  1901. * </p>
  1902. * @param value true if the application will perform a full recalculation of
  1903. * this worksheet values when the workbook is opened
  1904. *
  1905. * @see Workbook#setForceFormulaRecalculation(boolean)
  1906. */
  1907. @Override
  1908. public void setForceFormulaRecalculation(boolean value) {
  1909. CTCalcPr calcPr = getWorkbook().getCTWorkbook().getCalcPr();
  1910. if(worksheet.isSetSheetCalcPr()) {
  1911. // Change the current setting
  1912. CTSheetCalcPr calc = worksheet.getSheetCalcPr();
  1913. calc.setFullCalcOnLoad(value);
  1914. }
  1915. else if(value) {
  1916. // Add the Calc block and set it
  1917. CTSheetCalcPr calc = worksheet.addNewSheetCalcPr();
  1918. calc.setFullCalcOnLoad(value);
  1919. }
  1920. if(value && calcPr != null && calcPr.getCalcMode() == STCalcMode.MANUAL) {
  1921. calcPr.setCalcMode(STCalcMode.AUTO);
  1922. }
  1923. }
  1924. /**
  1925. * Whether Excel will be asked to recalculate all formulas of this sheet
  1926. * when the workbook is opened.
  1927. *
  1928. * Note: This just returns if the sheet has the recalculate flag set and
  1929. * will still return false even if recalculation is enabled on workbook-level.
  1930. *
  1931. * @return true if the Sheet has the recalculate-flag set.
  1932. */
  1933. @Override
  1934. public boolean getForceFormulaRecalculation() {
  1935. if(worksheet.isSetSheetCalcPr()) {
  1936. CTSheetCalcPr calc = worksheet.getSheetCalcPr();
  1937. return calc.getFullCalcOnLoad();
  1938. }
  1939. return false;
  1940. }
  1941. /**
  1942. * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not
  1943. * be the third row if say for instance the second row is undefined.
  1944. * Call getRowNum() on each row if you care which one it is.
  1945. */
  1946. @Override
  1947. @SuppressWarnings("unchecked")
  1948. public Iterator<Row> rowIterator() {
  1949. return (Iterator<Row>)(Iterator<? extends Row>) _rows.values().iterator();
  1950. }
  1951. /**
  1952. * @return a spliterator of the PHYSICAL rows. Meaning the 3rd element may not
  1953. * be the third row if say for instance the second row is undefined.
  1954. * Call getRowNum() on each row if you care which one it is.
  1955. *
  1956. * @since POI 5.2.0
  1957. */
  1958. @Override
  1959. @SuppressWarnings("unchecked")
  1960. public Spliterator<Row> spliterator() {
  1961. return (Spliterator<Row>)(Spliterator<? extends Row>) _rows.values().spliterator();
  1962. }
  1963. /**
  1964. * Flag indicating whether the sheet displays Automatic Page Breaks.
  1965. *
  1966. * @return {@code true} if the sheet displays Automatic Page Breaks.
  1967. */
  1968. @Override
  1969. public boolean getAutobreaks() {
  1970. CTSheetPr sheetPr = getSheetTypeSheetPr();
  1971. CTPageSetUpPr psSetup = (sheetPr == null || !sheetPr.isSetPageSetUpPr()) ?
  1972. CTPageSetUpPr.Factory.newInstance() : sheetPr.getPageSetUpPr();
  1973. return psSetup.getAutoPageBreaks();
  1974. }
  1975. /**
  1976. * Flag indicating whether the sheet displays Automatic Page Breaks.
  1977. *
  1978. * @param value {@code true} if the sheet displays Automatic Page Breaks.
  1979. */
  1980. @Override
  1981. public void setAutobreaks(boolean value) {
  1982. CTSheetPr sheetPr = getSheetTypeSheetPr();
  1983. CTPageSetUpPr psSetup = sheetPr.isSetPageSetUpPr() ? sheetPr.getPageSetUpPr() : sheetPr.addNewPageSetUpPr();
  1984. psSetup.setAutoPageBreaks(value);
  1985. }
  1986. /**
  1987. * Sets a page break at the indicated column.
  1988. * Breaks occur above the specified row and left of the specified column inclusive.
  1989. *
  1990. * For example, {@code sheet.setColumnBreak(2);} breaks the sheet into two parts
  1991. * with columns A,B,C in the first and D,E,... in the second. Similar, {@code sheet.setRowBreak(2);}
  1992. * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part
  1993. * and rows starting with rownum=4 in the second.
  1994. *
  1995. * @param column the column to break, inclusive
  1996. */
  1997. @Override
  1998. public void setColumnBreak(int column) {
  1999. if (!isColumnBroken(column)) {
  2000. CTPageBreak pgBreak = worksheet.isSetColBreaks() ? worksheet.getColBreaks() : worksheet.addNewColBreaks();
  2001. setBreak(column, pgBreak, SpreadsheetVersion.EXCEL2007.getLastRowIndex());
  2002. }
  2003. }
  2004. @Override
  2005. public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) {
  2006. if (collapsed) {
  2007. collapseColumn(columnNumber);
  2008. } else {
  2009. expandColumn(columnNumber);
  2010. }
  2011. }
  2012. private void collapseColumn(int columnNumber) {
  2013. CTCols cols = worksheet.getColsArray(0);
  2014. CTCol col = columnHelper.getColumn(columnNumber, false);
  2015. int colInfoIx = columnHelper.getIndexOfColumn(cols, col);
  2016. if (colInfoIx == -1) {
  2017. return;
  2018. }
  2019. // Find the start of the group.
  2020. int groupStartColInfoIx = findStartOfColumnOutlineGroup(colInfoIx);
  2021. CTCol columnInfo = cols.getColArray(groupStartColInfoIx);
  2022. // Hide all the columns until the end of the group
  2023. int lastColMax = setGroupHidden(groupStartColInfoIx, columnInfo
  2024. .getOutlineLevel(), true);
  2025. // write collapse field
  2026. setColumn(lastColMax + 1, 0, null, null, Boolean.TRUE);
  2027. }
  2028. private void setColumn(int targetColumnIx, Integer style,
  2029. Integer level, Boolean hidden, Boolean collapsed) {
  2030. CTCols cols = worksheet.getColsArray(0);
  2031. CTCol ci = null;
  2032. for (CTCol tci : cols.getColArray()) {
  2033. long tciMin = tci.getMin();
  2034. long tciMax = tci.getMax();
  2035. if (tciMin >= targetColumnIx && tciMax <= targetColumnIx) {
  2036. ci = tci;
  2037. break;
  2038. }
  2039. if (tciMin > targetColumnIx) {
  2040. // call column infos after k are for later columns
  2041. break; // exit now so k will be the correct insert pos
  2042. }
  2043. }
  2044. if (ci == null) {
  2045. // okay so there ISN'T a column info record that covers this column
  2046. // so lets create one!
  2047. CTCol nci = CTCol.Factory.newInstance();
  2048. nci.setMin(targetColumnIx);
  2049. nci.setMax(targetColumnIx);
  2050. unsetCollapsed(collapsed, nci);
  2051. this.columnHelper.addCleanColIntoCols(cols, nci);
  2052. return;
  2053. }
  2054. boolean styleChanged = style != null && ci.getStyle() != style;
  2055. boolean levelChanged = level != null && ci.getOutlineLevel() != level;
  2056. boolean hiddenChanged = hidden != null && ci.getHidden() != hidden;
  2057. boolean collapsedChanged = collapsed != null && ci.getCollapsed() != collapsed;
  2058. boolean columnChanged = levelChanged || hiddenChanged || collapsedChanged || styleChanged;
  2059. if (!columnChanged) {
  2060. // do nothing...nothing changed.
  2061. return;
  2062. }
  2063. long ciMin = ci.getMin();
  2064. long ciMax = ci.getMax();
  2065. if (ciMin == targetColumnIx && ciMax == targetColumnIx) {
  2066. // ColumnInfo ci for a single column, the target column
  2067. unsetCollapsed(collapsed, ci);
  2068. return;
  2069. }
  2070. if (ciMin == targetColumnIx || ciMax == targetColumnIx) {
  2071. // The target column is at either end of the multi-column ColumnInfo ci
  2072. // we'll just divide the info and create a new one
  2073. if (ciMin == targetColumnIx) {
  2074. ci.setMin(targetColumnIx + 1L);
  2075. } else {
  2076. ci.setMax(targetColumnIx - 1L);
  2077. }
  2078. CTCol nci = columnHelper.cloneCol(cols, ci);
  2079. nci.setMin(targetColumnIx);
  2080. unsetCollapsed(collapsed, nci);
  2081. this.columnHelper.addCleanColIntoCols(cols, nci);
  2082. } else {
  2083. // split to 3 records
  2084. CTCol ciMid = columnHelper.cloneCol(cols, ci);
  2085. CTCol ciEnd = columnHelper.cloneCol(cols, ci);
  2086. int lastcolumn = Math.toIntExact(ciMax);
  2087. ci.setMax(targetColumnIx - 1L);
  2088. ciMid.setMin(targetColumnIx);
  2089. ciMid.setMax(targetColumnIx);
  2090. unsetCollapsed(collapsed, ciMid);
  2091. this.columnHelper.addCleanColIntoCols(cols, ciMid);
  2092. ciEnd.setMin(targetColumnIx + 1L);
  2093. ciEnd.setMax(lastcolumn);
  2094. this.columnHelper.addCleanColIntoCols(cols, ciEnd);
  2095. }
  2096. }
  2097. private void unsetCollapsed(Boolean collapsed, CTCol ci) {
  2098. if (collapsed != null && collapsed) {
  2099. ci.setCollapsed(true);
  2100. } else {
  2101. ci.unsetCollapsed();
  2102. }
  2103. }
  2104. /**
  2105. * Sets all adjacent columns of the same outline level to the specified
  2106. * hidden status.
  2107. *
  2108. * @param pIdx
  2109. * the col info index of the start of the outline group
  2110. * @return the column index of the last column in the outline group
  2111. */
  2112. private int setGroupHidden(int pIdx, int level, boolean hidden) {
  2113. CTCols cols = worksheet.getColsArray(0);
  2114. int idx = pIdx;
  2115. CTCol[] colArray = cols.getColArray();
  2116. CTCol columnInfo = colArray[idx];
  2117. while (idx < colArray.length) {
  2118. columnInfo.setHidden(hidden);
  2119. if (idx + 1 < colArray.length) {
  2120. CTCol nextColumnInfo = colArray[idx + 1];
  2121. if (!isAdjacentBefore(columnInfo, nextColumnInfo)) {
  2122. break;
  2123. }
  2124. if (nextColumnInfo.getOutlineLevel() < level) {
  2125. break;
  2126. }
  2127. columnInfo = nextColumnInfo;
  2128. }
  2129. idx++;
  2130. }
  2131. return Math.toIntExact(columnInfo.getMax());
  2132. }
  2133. private boolean isAdjacentBefore(CTCol col, CTCol otherCol) {
  2134. return col.getMax() == otherCol.getMin() - 1;
  2135. }
  2136. private int findStartOfColumnOutlineGroup(int pIdx) {
  2137. // Find the start of the group.
  2138. CTCols cols = worksheet.getColsArray(0);
  2139. CTCol[] colArray = cols.getColArray();
  2140. CTCol columnInfo = colArray[pIdx];
  2141. int level = columnInfo.getOutlineLevel();
  2142. int idx = pIdx;
  2143. while (idx != 0) {
  2144. CTCol prevColumnInfo = colArray[idx - 1];
  2145. if (!isAdjacentBefore(prevColumnInfo, columnInfo)) {
  2146. break;
  2147. }
  2148. if (prevColumnInfo.getOutlineLevel() < level) {
  2149. break;
  2150. }
  2151. idx--;
  2152. columnInfo = prevColumnInfo;
  2153. }
  2154. return idx;
  2155. }
  2156. private int findEndOfColumnOutlineGroup(int colInfoIndex) {
  2157. CTCols cols = worksheet.getColsArray(0);
  2158. // Find the end of the group.
  2159. CTCol[] colArray = cols.getColArray();
  2160. CTCol columnInfo = colArray[colInfoIndex];
  2161. int level = columnInfo.getOutlineLevel();
  2162. int idx = colInfoIndex;
  2163. int lastIdx = colArray.length - 1;
  2164. while (idx < lastIdx) {
  2165. CTCol nextColumnInfo = colArray[idx + 1];
  2166. if (!isAdjacentBefore(columnInfo, nextColumnInfo)) {
  2167. break;
  2168. }
  2169. if (nextColumnInfo.getOutlineLevel() < level) {
  2170. break;
  2171. }
  2172. idx++;
  2173. columnInfo = nextColumnInfo;
  2174. }
  2175. return idx;
  2176. }
  2177. private void expandColumn(int columnIndex) {
  2178. CTCols cols = worksheet.getColsArray(0);
  2179. CTCol col = columnHelper.getColumn(columnIndex, false);
  2180. int colInfoIx = columnHelper.getIndexOfColumn(cols, col);
  2181. int idx = col == null ? -1 : findColInfoIdx(Math.toIntExact(col.getMax()), colInfoIx);
  2182. if (idx == -1) {
  2183. return;
  2184. }
  2185. // If it is already expanded do nothing.
  2186. if (!isColumnGroupCollapsed(idx)) {
  2187. return;
  2188. }
  2189. // Find the start/end of the group.
  2190. int startIdx = findStartOfColumnOutlineGroup(idx);
  2191. int endIdx = findEndOfColumnOutlineGroup(idx);
  2192. // expand:
  2193. // collapsed bit must be unset
  2194. // hidden bit gets unset _if_ surrounding groups are expanded you can
  2195. // determine
  2196. // this by looking at the hidden bit of the enclosing group. You will
  2197. // have
  2198. // to look at the start and the end of the current group to determine
  2199. // which
  2200. // is the enclosing group
  2201. // hidden bit only is altered for this outline level. ie. don't
  2202. // uncollapse contained groups
  2203. CTCol[] colArray = cols.getColArray();
  2204. CTCol columnInfo = colArray[endIdx];
  2205. if (!isColumnGroupHiddenByParent(idx)) {
  2206. short outlineLevel = columnInfo.getOutlineLevel();
  2207. boolean nestedGroup = false;
  2208. for (int i = startIdx; i <= endIdx; i++) {
  2209. CTCol ci = colArray[i];
  2210. if (outlineLevel == ci.getOutlineLevel()) {
  2211. ci.unsetHidden();
  2212. if (nestedGroup) {
  2213. nestedGroup = false;
  2214. ci.setCollapsed(true);
  2215. }
  2216. } else {
  2217. nestedGroup = true;
  2218. }
  2219. }
  2220. }
  2221. // Write collapse flag (stored in a single col info record after this
  2222. // outline group)
  2223. setColumn(Math.toIntExact(columnInfo.getMax() + 1), null, null,
  2224. Boolean.FALSE, Boolean.FALSE);
  2225. }
  2226. private boolean isColumnGroupHiddenByParent(int idx) {
  2227. CTCols cols = worksheet.getColsArray(0);
  2228. // Look out outline details of end
  2229. int endLevel = 0;
  2230. boolean endHidden = false;
  2231. int endOfOutlineGroupIdx = findEndOfColumnOutlineGroup(idx);
  2232. CTCol[] colArray = cols.getColArray();
  2233. if (endOfOutlineGroupIdx < (colArray.length - 1)) {
  2234. CTCol nextInfo = colArray[endOfOutlineGroupIdx + 1];
  2235. if (isAdjacentBefore(colArray[endOfOutlineGroupIdx], nextInfo)) {
  2236. endLevel = nextInfo.getOutlineLevel();
  2237. endHidden = nextInfo.getHidden();
  2238. }
  2239. }
  2240. // Look out outline details of start
  2241. int startLevel = 0;
  2242. boolean startHidden = false;
  2243. int startOfOutlineGroupIdx = findStartOfColumnOutlineGroup(idx);
  2244. if (startOfOutlineGroupIdx > 0) {
  2245. CTCol prevInfo = colArray[startOfOutlineGroupIdx - 1];
  2246. if (isAdjacentBefore(prevInfo, colArray[startOfOutlineGroupIdx])) {
  2247. startLevel = prevInfo.getOutlineLevel();
  2248. startHidden = prevInfo.getHidden();
  2249. }
  2250. }
  2251. if (endLevel > startLevel) {
  2252. return endHidden;
  2253. }
  2254. return startHidden;
  2255. }
  2256. private int findColInfoIdx(int columnValue, int fromColInfoIdx) {
  2257. CTCols cols = worksheet.getColsArray(0);
  2258. if (columnValue < 0) {
  2259. throw new IllegalArgumentException(
  2260. "column parameter out of range: " + columnValue);
  2261. }
  2262. if (fromColInfoIdx < 0) {
  2263. throw new IllegalArgumentException(
  2264. "fromIdx parameter out of range: " + fromColInfoIdx);
  2265. }
  2266. CTCol[] colArray = cols.getColArray();
  2267. for (int k = fromColInfoIdx; k < colArray.length; k++) {
  2268. CTCol ci = colArray[k];
  2269. if (containsColumn(ci, columnValue)) {
  2270. return k;
  2271. }
  2272. if (ci.getMin() > fromColInfoIdx) {
  2273. break;
  2274. }
  2275. }
  2276. return -1;
  2277. }
  2278. private boolean containsColumn(CTCol col, int columnIndex) {
  2279. return col.getMin() <= columnIndex && columnIndex <= col.getMax();
  2280. }
  2281. /**
  2282. * 'Collapsed' state is stored in a single column col info record
  2283. * immediately after the outline group
  2284. *
  2285. * @param idx The column-index to check
  2286. * @return a boolean represented if the column is collapsed
  2287. */
  2288. private boolean isColumnGroupCollapsed(int idx) {
  2289. CTCols cols = worksheet.getColsArray(0);
  2290. CTCol[] colArray = cols.getColArray();
  2291. int endOfOutlineGroupIdx = findEndOfColumnOutlineGroup(idx);
  2292. int nextColInfoIx = endOfOutlineGroupIdx + 1;
  2293. if (nextColInfoIx >= colArray.length) {
  2294. return false;
  2295. }
  2296. CTCol nextColInfo = colArray[nextColInfoIx];
  2297. CTCol col = colArray[endOfOutlineGroupIdx];
  2298. if (!isAdjacentBefore(col, nextColInfo)) {
  2299. return false;
  2300. }
  2301. return nextColInfo.getCollapsed();
  2302. }
  2303. /**
  2304. * Set the visibility state for a given column.
  2305. *
  2306. * @param columnIndex - the column to get (0-based)
  2307. * @param hidden - the visibility state of the column
  2308. */
  2309. @Override
  2310. public void setColumnHidden(int columnIndex, boolean hidden) {
  2311. columnHelper.setColHidden(columnIndex, hidden);
  2312. }
  2313. /**
  2314. * Set the width (in units of 1/256th of a character width)
  2315. *
  2316. * <p>
  2317. * The maximum column width for an individual cell is 255 characters.
  2318. * This value represents the number of characters that can be displayed
  2319. * in a cell that is formatted with the standard font (first font in the workbook).
  2320. * </p>
  2321. *
  2322. * <p>
  2323. * Character width is defined as the maximum digit width
  2324. * of the numbers {@code 0, 1, 2, ... 9} as rendered
  2325. * using the default font (first font in the workbook).
  2326. * <br>
  2327. * Unless you are using a very special font, the default character is '0' (zero),
  2328. * this is true for Arial (default font in HSSF) and Calibri (default font in XSSF)
  2329. * </p>
  2330. *
  2331. * <p>
  2332. * Please note, that the width set by this method includes 4 pixels of margin padding (two on each side),
  2333. * plus 1 pixel padding for the gridlines (Section 3.3.1.12 of the OOXML spec).
  2334. * This results is a slightly less value of visible characters than passed to this method (approx. 1/2 of a character).
  2335. * </p>
  2336. * <p>
  2337. * To compute the actual number of visible characters,
  2338. * Excel uses the following formula (Section 3.3.1.12 of the OOXML spec):
  2339. * </p>
  2340. * {@code
  2341. * width = Truncate([{Number of Visible Characters} *
  2342. * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256
  2343. * }
  2344. * <p>Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi).
  2345. * If you set a column width to be eight characters wide, e.g. {@code setColumnWidth(columnIndex, 8*256)},
  2346. * then the actual value of visible characters (the value shown in Excel) is derived from the following equation:
  2347. * {@code
  2348. Truncate([numChars*7+5]/7*256)/256 = 8;
  2349. * }
  2350. *
  2351. * which gives {@code 7.29}.
  2352. *
  2353. * @param columnIndex - the column to set (0-based)
  2354. * @param width - the width in units of 1/256th of a character width
  2355. * @throws IllegalArgumentException if width &gt; 255*256 (the maximum column width in Excel is 255 characters)
  2356. */
  2357. @Override
  2358. public void setColumnWidth(int columnIndex, int width) {
  2359. if(width > 255*256) {
  2360. throw new IllegalArgumentException("The maximum column width for an individual cell is 255 characters.");
  2361. }
  2362. columnHelper.setColWidth(columnIndex, (double)width/256);
  2363. columnHelper.setCustomWidth(columnIndex, true);
  2364. }
  2365. @Override
  2366. public void setDefaultColumnStyle(int column, CellStyle style) {
  2367. columnHelper.setColDefaultStyle(column, style);
  2368. }
  2369. /**
  2370. * Specifies the number of characters of the maximum digit width of the normal style's font.
  2371. * This value does not include margin padding or extra padding for gridlines. It is only the
  2372. * number of characters.
  2373. *
  2374. * @param width the number of characters. Default value is {@code 8}.
  2375. */
  2376. @Override
  2377. public void setDefaultColumnWidth(int width) {
  2378. getSheetTypeSheetFormatPr(true).setBaseColWidth(width);
  2379. }
  2380. /**
  2381. * Set the default row height for the sheet (if the rows do not define their own height) in
  2382. * twips (1/20 of a point)
  2383. *
  2384. * @param height default row height in twips (1/20 of a point)
  2385. */
  2386. @Override
  2387. public void setDefaultRowHeight(short height) {
  2388. setDefaultRowHeightInPoints((float)height / Font.TWIPS_PER_POINT);
  2389. }
  2390. /**
  2391. * Sets default row height measured in point size.
  2392. *
  2393. * @param height default row height measured in point size.
  2394. */
  2395. @Override
  2396. public void setDefaultRowHeightInPoints(float height) {
  2397. CTSheetFormatPr pr = getSheetTypeSheetFormatPr(true);
  2398. pr.setDefaultRowHeight(height);
  2399. pr.setCustomHeight(true);
  2400. }
  2401. /**
  2402. * Sets the flag indicating whether this sheet should display formulas.
  2403. *
  2404. * @param show {@code true} if this sheet should display formulas.
  2405. */
  2406. @Override
  2407. public void setDisplayFormulas(boolean show) {
  2408. final CTSheetView dsv = getDefaultSheetView(true);
  2409. assert(dsv != null);
  2410. dsv.setShowFormulas(show);
  2411. }
  2412. /**
  2413. * Flag indicating whether the Fit to Page print option is enabled.
  2414. *
  2415. * @param b {@code true} if the Fit to Page print option is enabled.
  2416. */
  2417. @Override
  2418. public void setFitToPage(boolean b) {
  2419. getSheetTypePageSetUpPr().setFitToPage(b);
  2420. }
  2421. /**
  2422. * Center on page horizontally when printing.
  2423. *
  2424. * @param value whether to center on page horizontally when printing.
  2425. */
  2426. @Override
  2427. public void setHorizontallyCenter(boolean value) {
  2428. CTPrintOptions opts = worksheet.isSetPrintOptions() ?
  2429. worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
  2430. opts.setHorizontalCentered(value);
  2431. }
  2432. /**
  2433. * Whether the output is vertically centered on the page.
  2434. *
  2435. * @param value true to vertically center, false otherwise.
  2436. */
  2437. @Override
  2438. public void setVerticallyCenter(boolean value) {
  2439. CTPrintOptions opts = worksheet.isSetPrintOptions() ?
  2440. worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
  2441. opts.setVerticalCentered(value);
  2442. }
  2443. /**
  2444. * group the row It is possible for collapsed to be false and yet still have
  2445. * the rows in question hidden. This can be achieved by having a lower
  2446. * outline level collapsed, thus hiding all the child rows. Note that in
  2447. * this case, if the lowest level were expanded, the middle level would
  2448. * remain collapsed.
  2449. *
  2450. * @param rowIndex -
  2451. * the row involved, 0 based
  2452. * @param collapse -
  2453. * boolean value for collapse
  2454. */
  2455. @Override
  2456. public void setRowGroupCollapsed(int rowIndex, boolean collapse) {
  2457. if (collapse) {
  2458. collapseRow(rowIndex);
  2459. } else {
  2460. expandRow(rowIndex);
  2461. }
  2462. }
  2463. /**
  2464. * @param rowIndex the zero based row index to collapse
  2465. */
  2466. private void collapseRow(int rowIndex) {
  2467. XSSFRow row = getRow(rowIndex);
  2468. if (row != null) {
  2469. int startRow = findStartOfRowOutlineGroup(rowIndex);
  2470. // Hide all the columns until the end of the group
  2471. int lastRow = writeHidden(row, startRow, true);
  2472. if (getRow(lastRow) != null) {
  2473. getRow(lastRow).getCTRow().setCollapsed(true);
  2474. } else {
  2475. XSSFRow newRow = createRow(lastRow);
  2476. newRow.getCTRow().setCollapsed(true);
  2477. }
  2478. }
  2479. }
  2480. /**
  2481. * @param rowIndex the zero based row index to find from
  2482. */
  2483. private int findStartOfRowOutlineGroup(int rowIndex) {
  2484. // Find the start of the group.
  2485. short level = getRow(rowIndex).getCTRow().getOutlineLevel();
  2486. int currentRow = rowIndex;
  2487. while (getRow(currentRow) != null) {
  2488. if (getRow(currentRow).getCTRow().getOutlineLevel() < level) {
  2489. return currentRow + 1;
  2490. }
  2491. currentRow--;
  2492. }
  2493. return currentRow;
  2494. }
  2495. private int writeHidden(XSSFRow xRow, int rowIndex, boolean hidden) {
  2496. short level = xRow.getCTRow().getOutlineLevel();
  2497. for (Iterator<Row> it = rowIterator(); it.hasNext();) {
  2498. xRow = (XSSFRow) it.next();
  2499. // skip rows before the start of this group
  2500. if(xRow.getRowNum() < rowIndex) {
  2501. continue;
  2502. }
  2503. if (xRow.getCTRow().getOutlineLevel() >= level) {
  2504. xRow.getCTRow().setHidden(hidden);
  2505. rowIndex++;
  2506. }
  2507. }
  2508. return rowIndex;
  2509. }
  2510. /**
  2511. * @param rowNumber the zero based row index to expand
  2512. */
  2513. private void expandRow(int rowNumber) {
  2514. if (rowNumber == -1) {
  2515. return;
  2516. }
  2517. XSSFRow row = getRow(rowNumber);
  2518. // If it is already expanded do nothing.
  2519. if (!row.getCTRow().isSetHidden()) {
  2520. return;
  2521. }
  2522. // Find the start of the group.
  2523. int startIdx = findStartOfRowOutlineGroup(rowNumber);
  2524. // Find the end of the group.
  2525. int endIdx = findEndOfRowOutlineGroup(rowNumber);
  2526. // expand:
  2527. // collapsed must be unset
  2528. // hidden bit gets unset _if_ surrounding groups are expanded you can
  2529. // determine
  2530. // this by looking at the hidden bit of the enclosing group. You will
  2531. // have
  2532. // to look at the start and the end of the current group to determine
  2533. // which
  2534. // is the enclosing group
  2535. // hidden bit only is altered for this outline level. ie. don't
  2536. // un-collapse contained groups
  2537. short level = row.getCTRow().getOutlineLevel();
  2538. if (!isRowGroupHiddenByParent(rowNumber)) {
  2539. for (int i = startIdx; i < endIdx; i++) {
  2540. if (level == getRow(i).getCTRow().getOutlineLevel()) {
  2541. getRow(i).getCTRow().unsetHidden();
  2542. } else if (!isRowGroupCollapsed(i)) {
  2543. getRow(i).getCTRow().unsetHidden();
  2544. }
  2545. }
  2546. }
  2547. // Write collapse field
  2548. CTRow ctRow = getRow(endIdx).getCTRow();
  2549. // This avoids an IndexOutOfBounds if multiple nested groups are collapsed/expanded
  2550. if(ctRow.getCollapsed()) {
  2551. ctRow.unsetCollapsed();
  2552. }
  2553. }
  2554. /**
  2555. * @param row the zero based row index to find from
  2556. */
  2557. public int findEndOfRowOutlineGroup(int row) {
  2558. short level = getRow(row).getCTRow().getOutlineLevel();
  2559. int currentRow;
  2560. final int lastRowNum = getLastRowNum();
  2561. for (currentRow = row; currentRow < lastRowNum; currentRow++) {
  2562. if (getRow(currentRow) == null
  2563. || getRow(currentRow).getCTRow().getOutlineLevel() < level) {
  2564. break;
  2565. }
  2566. }
  2567. return currentRow;
  2568. }
  2569. /**
  2570. * @param row the zero based row index to find from
  2571. */
  2572. private boolean isRowGroupHiddenByParent(int row) {
  2573. // Look out outline details of end
  2574. int endLevel;
  2575. boolean endHidden;
  2576. int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row);
  2577. if (getRow(endOfOutlineGroupIdx) == null) {
  2578. endLevel = 0;
  2579. endHidden = false;
  2580. } else {
  2581. endLevel = getRow(endOfOutlineGroupIdx).getCTRow().getOutlineLevel();
  2582. endHidden = getRow(endOfOutlineGroupIdx).getCTRow().getHidden();
  2583. }
  2584. // Look out outline details of start
  2585. int startLevel;
  2586. boolean startHidden;
  2587. int startOfOutlineGroupIdx = findStartOfRowOutlineGroup(row);
  2588. if (startOfOutlineGroupIdx < 0
  2589. || getRow(startOfOutlineGroupIdx) == null) {
  2590. startLevel = 0;
  2591. startHidden = false;
  2592. } else {
  2593. startLevel = getRow(startOfOutlineGroupIdx).getCTRow()
  2594. .getOutlineLevel();
  2595. startHidden = getRow(startOfOutlineGroupIdx).getCTRow()
  2596. .getHidden();
  2597. }
  2598. if (endLevel > startLevel) {
  2599. return endHidden;
  2600. }
  2601. return startHidden;
  2602. }
  2603. /**
  2604. * @param row the zero based row index to find from
  2605. */
  2606. private boolean isRowGroupCollapsed(int row) {
  2607. int collapseRow = findEndOfRowOutlineGroup(row) + 1;
  2608. if (getRow(collapseRow) == null) {
  2609. return false;
  2610. }
  2611. return getRow(collapseRow).getCTRow().getCollapsed();
  2612. }
  2613. /**
  2614. * Window zoom magnification for current view representing percent values.
  2615. * Valid values range from 10 to 400. Horizontal &amp; Vertical scale together.
  2616. *
  2617. * For example:
  2618. * <pre>
  2619. * 10 - 10%
  2620. * 20 - 20%
  2621. * ...
  2622. * 100 - 100%
  2623. * ...
  2624. * 400 - 400%
  2625. * </pre>
  2626. *
  2627. * Current view can be Normal, Page Layout, or Page Break Preview.
  2628. *
  2629. * @param scale window zoom magnification
  2630. * @throws IllegalArgumentException if scale is invalid
  2631. */
  2632. @Override
  2633. public void setZoom(int scale) {
  2634. if (scale < 10 || scale > 400) {
  2635. throw new IllegalArgumentException("Valid scale values range from 10 to 400");
  2636. }
  2637. final CTSheetView dsv = getDefaultSheetView(true);
  2638. assert(dsv != null);
  2639. dsv.setZoomScale(scale);
  2640. }
  2641. /**
  2642. * copyRows rows from srcRows to this sheet starting at destStartRow
  2643. *
  2644. * Additionally copies merged regions that are completely defined in these
  2645. * rows (ie. merged 2 cells on a row to be shifted).
  2646. * @param srcRows the rows to copy. Formulas will be offset by the difference
  2647. * in the row number of the first row in srcRows and destStartRow (even if srcRows
  2648. * are from a different sheet).
  2649. * @param destStartRow the row in this sheet to paste the first row of srcRows
  2650. * the remainder of srcRows will be pasted below destStartRow per the cell copy policy
  2651. * @param policy is the cell copy policy, which can be used to merge the source and destination
  2652. * when the source is blank, copy styles only, paste as value, etc
  2653. */
  2654. @Beta
  2655. public void copyRows(List<? extends Row> srcRows, int destStartRow, CellCopyPolicy policy) {
  2656. if (srcRows == null || srcRows.isEmpty()) {
  2657. throw new IllegalArgumentException("No rows to copy");
  2658. }
  2659. final Row srcStartRow = srcRows.get(0);
  2660. final Row srcEndRow = srcRows.get(srcRows.size() - 1);
  2661. if (srcStartRow == null) {
  2662. throw new IllegalArgumentException("copyRows: First row cannot be null");
  2663. }
  2664. final int srcStartRowNum = srcStartRow.getRowNum();
  2665. final int srcEndRowNum = srcEndRow.getRowNum();
  2666. // check row numbers to make sure they are continuous and increasing (monotonic)
  2667. // and srcRows does not contain null rows
  2668. final int size = srcRows.size();
  2669. for (int index=1; index < size; index++) {
  2670. final Row curRow = srcRows.get(index);
  2671. if (curRow == null) {
  2672. throw new IllegalArgumentException("srcRows may not contain null rows. Found null row at index " + index + ".");
  2673. //} else if (curRow.getRowNum() != prevRow.getRowNum() + 1) {
  2674. // throw new IllegalArgumentException("srcRows must contain continuously increasing row numbers. " +
  2675. // "Got srcRows[" + (index-1) + "]=Row " + prevRow.getRowNum() + ", srcRows[" + index + "]=Row " + curRow.getRowNum() + ".");
  2676. // FIXME: assumes row objects belong to non-null sheets and sheets belong to non-null workbooks.
  2677. } else if (srcStartRow.getSheet().getWorkbook() != curRow.getSheet().getWorkbook()) {
  2678. throw new IllegalArgumentException("All rows in srcRows must belong to the same sheet in the same workbook. " +
  2679. "Expected all rows from same workbook (" + srcStartRow.getSheet().getWorkbook() + "). " +
  2680. "Got srcRows[" + index + "] from different workbook (" + curRow.getSheet().getWorkbook() + ").");
  2681. } else if (srcStartRow.getSheet() != curRow.getSheet()) {
  2682. throw new IllegalArgumentException("All rows in srcRows must belong to the same sheet. " +
  2683. "Expected all rows from " + srcStartRow.getSheet().getSheetName() + ". " +
  2684. "Got srcRows[" + index + "] from " + curRow.getSheet().getSheetName());
  2685. }
  2686. }
  2687. // FIXME: is special behavior needed if srcRows and destRows belong to the same sheets and the regions overlap?
  2688. final CellCopyPolicy options = new CellCopyPolicy(policy);
  2689. // avoid O(N^2) performance scanning through all regions for each row
  2690. // merged regions will be copied after all the rows have been copied
  2691. options.setCopyMergedRegions(false);
  2692. // FIXME: if srcRows contains gaps or null values, clear out those rows that will be overwritten
  2693. // how will this work with merging (copy just values, leave cell styles in place?)
  2694. int r = destStartRow;
  2695. for (Row srcRow : srcRows) {
  2696. int destRowNum;
  2697. if (policy.isCondenseRows()) {
  2698. destRowNum = r++;
  2699. } else {
  2700. final int shift = (srcRow.getRowNum() - srcStartRowNum);
  2701. destRowNum = destStartRow + shift;
  2702. }
  2703. //removeRow(destRowNum); //this probably clears all external formula references to destRow, causing unwanted #REF! errors
  2704. final XSSFRow destRow = createRow(destRowNum);
  2705. destRow.copyRowFrom(srcRow, options);
  2706. }
  2707. // ======================
  2708. // Only do additional copy operations here that cannot be done with Row.copyFromRow(Row, options)
  2709. // reasons: operation needs to interact with multiple rows or sheets
  2710. // Copy merged regions that are contained within the copy region
  2711. if (policy.isCopyMergedRegions()) {
  2712. // FIXME: is this something that rowShifter could be doing?
  2713. final int shift = destStartRow - srcStartRowNum;
  2714. for (CellRangeAddress srcRegion : srcStartRow.getSheet().getMergedRegions()) {
  2715. if (srcStartRowNum <= srcRegion.getFirstRow() && srcRegion.getLastRow() <= srcEndRowNum) {
  2716. // srcRegion is fully inside the copied rows
  2717. final CellRangeAddress destRegion = srcRegion.copy();
  2718. destRegion.setFirstRow(destRegion.getFirstRow() + shift);
  2719. destRegion.setLastRow(destRegion.getLastRow() + shift);
  2720. addMergedRegion(destRegion);
  2721. }
  2722. }
  2723. }
  2724. }
  2725. /**
  2726. * Copies rows between srcStartRow and srcEndRow to the same sheet, starting at destStartRow
  2727. * Convenience function for {@link #copyRows(List, int, CellCopyPolicy)}
  2728. *
  2729. * Equivalent to copyRows(getRows(srcStartRow, srcEndRow, false), destStartRow, cellCopyPolicy)
  2730. *
  2731. * @param srcStartRow the index of the first row to copy the cells from in this sheet
  2732. * @param srcEndRow the index of the last row to copy the cells from in this sheet
  2733. * @param destStartRow the index of the first row to copy the cells to in this sheet
  2734. * @param cellCopyPolicy the policy to use to determine how cells are copied
  2735. */
  2736. @Beta
  2737. public void copyRows(int srcStartRow, int srcEndRow, int destStartRow, CellCopyPolicy cellCopyPolicy) {
  2738. final List<XSSFRow> srcRows = getRows(srcStartRow, srcEndRow, false); //FIXME: should be false, no need to create rows where src is only to copy them to dest
  2739. copyRows(srcRows, destStartRow, cellCopyPolicy);
  2740. }
  2741. /**
  2742. * Shifts rows between startRow and endRow n number of rows.
  2743. * If you use a negative number, it will shift rows up.
  2744. * Code ensures that rows don't wrap around.
  2745. *
  2746. * Calls shiftRows(startRow, endRow, n, false, false);
  2747. *
  2748. * <p>
  2749. * Additionally, shifts merged regions that are completely defined in these
  2750. * rows (i.e. merged 2 cells on a row to be shifted).
  2751. * @param startRow the row to start shifting
  2752. * @param endRow the row to end shifting
  2753. * @param n the number of rows to shift
  2754. */
  2755. @Override
  2756. public void shiftRows(int startRow, int endRow, int n) {
  2757. shiftRows(startRow, endRow, n, false, false);
  2758. }
  2759. /**
  2760. * Shifts rows between startRow and endRow n number of rows.
  2761. * If you use a negative number, it will shift rows up.
  2762. * Code ensures that rows don't wrap around
  2763. *
  2764. * <p>
  2765. * Additionally, shifts merged regions that are completely defined in these
  2766. * rows (i.e. merged 2 cells on a row to be shifted). All merged regions that are
  2767. * completely overlaid by shifting will be deleted.
  2768. *
  2769. * @param startRow the row to start shifting
  2770. * @param endRow the row to end shifting
  2771. * @param n the number of rows to shift
  2772. * @param copyRowHeight whether to copy the row height during the shift
  2773. * @param resetOriginalRowHeight whether to set the original row's height to the default
  2774. */
  2775. @Override
  2776. public void shiftRows(int startRow, int endRow, final int n, boolean copyRowHeight, boolean resetOriginalRowHeight) {
  2777. List<XSSFTable> overlappingTables = new ArrayList<>();
  2778. for (XSSFTable table : getTables()) {
  2779. if ((table.getStartRowIndex() < startRow && table.getEndRowIndex() < startRow)
  2780. || (table.getStartRowIndex() > endRow && table.getEndRowIndex() > endRow)) {
  2781. // not overlapping
  2782. } else {
  2783. overlappingTables.add(table);
  2784. }
  2785. }
  2786. int sheetIndex = getWorkbook().getSheetIndex(this);
  2787. String sheetName = getWorkbook().getSheetName(sheetIndex);
  2788. FormulaShifter formulaShifter = FormulaShifter.createForRowShift(
  2789. sheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL2007);
  2790. removeOverwritten(startRow, endRow, n);
  2791. shiftCommentsAndRows(startRow, endRow, n);
  2792. XSSFRowShifter rowShifter = new XSSFRowShifter(this);
  2793. rowShifter.shiftMergedRegions(startRow, endRow, n);
  2794. rowShifter.updateNamedRanges(formulaShifter);
  2795. rowShifter.updateFormulas(formulaShifter);
  2796. rowShifter.updateConditionalFormatting(formulaShifter);
  2797. rowShifter.updateHyperlinks(formulaShifter);
  2798. rebuildRows();
  2799. for (XSSFTable table : overlappingTables) {
  2800. rebuildTableFormulas(table);
  2801. }
  2802. }
  2803. /**
  2804. * Shifts columns between startColumn and endColumn n number of columns.
  2805. * If you use a negative number, it will shift columns left.
  2806. * Code ensures that columns don't wrap around
  2807. *
  2808. * @param startColumn the column to start shifting
  2809. * @param endColumn the column to end shifting
  2810. * @param n length of the shifting step
  2811. */
  2812. @Override
  2813. public void shiftColumns(int startColumn, int endColumn, final int n) {
  2814. List<XSSFTable> overlappingTables = new ArrayList<>();
  2815. for (XSSFTable table : getTables()) {
  2816. if ((table.getStartColIndex() < startColumn && table.getEndColIndex() < startColumn)
  2817. || (table.getStartColIndex() > endColumn && table.getEndColIndex() > endColumn)) {
  2818. // not overlapping
  2819. } else {
  2820. overlappingTables.add(table);
  2821. }
  2822. }
  2823. XSSFVMLDrawing vml = getVMLDrawing(false);
  2824. shiftCommentsForColumns(vml, startColumn, endColumn, n);
  2825. FormulaShifter formulaShifter = FormulaShifter.createForColumnShift(this.getWorkbook().getSheetIndex(this), this.getSheetName(), startColumn, endColumn, n, SpreadsheetVersion.EXCEL2007);
  2826. XSSFColumnShifter columnShifter = new XSSFColumnShifter(this);
  2827. columnShifter.shiftColumns(startColumn, endColumn, n);
  2828. columnShifter.shiftMergedRegions(startColumn, endColumn, n);
  2829. columnShifter.updateFormulas(formulaShifter);
  2830. columnShifter.updateConditionalFormatting(formulaShifter);
  2831. columnShifter.updateHyperlinks(formulaShifter);
  2832. columnShifter.updateNamedRanges(formulaShifter);
  2833. rebuildRows();
  2834. for (XSSFTable table : overlappingTables) {
  2835. rebuildTableFormulas(table);
  2836. }
  2837. }
  2838. private void rebuildTableFormulas(XSSFTable table) {
  2839. //correct all sheet table-reference-formulas which probably got damaged after shift rows/columns
  2840. for (CTTableColumn tableCol : table.getCTTable().getTableColumns().getTableColumnList()) {
  2841. if (tableCol.getCalculatedColumnFormula() != null) {
  2842. int id = Math.toIntExact(tableCol.getId());
  2843. String formula = tableCol.getCalculatedColumnFormula().getStringValue();
  2844. int rFirst = table.getStartCellReference().getRow() + table.getHeaderRowCount();
  2845. int rLast = table.getEndCellReference().getRow() - table.getTotalsRowCount();
  2846. int c = table.getStartCellReference().getCol() + id - 1;
  2847. final boolean cellFormulaValidationFlag = getWorkbook().getCellFormulaValidation();
  2848. try {
  2849. getWorkbook().setCellFormulaValidation(false);
  2850. for (int r = rFirst; r <= rLast; r++) {
  2851. XSSFRow row = getRow(r);
  2852. if (row == null) row = createRow(r);
  2853. XSSFCell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
  2854. cell.setCellFormula(formula);
  2855. }
  2856. } finally {
  2857. getWorkbook().setCellFormulaValidation(cellFormulaValidationFlag);
  2858. }
  2859. }
  2860. }
  2861. }
  2862. private void rebuildRows() {
  2863. //rebuild the CTSheetData CTRow order
  2864. SortedMap<Long, CTRow> ctRows = new TreeMap<>();
  2865. CTSheetData sheetData = getCTWorksheet().getSheetData();
  2866. for (CTRow ctRow : sheetData.getRowList()) {
  2867. Long rownumL = ctRow.getR();
  2868. ctRows.put(rownumL, ctRow);
  2869. }
  2870. List<CTRow> ctRowList = new ArrayList<>(ctRows.values());
  2871. CTRow[] ctRowArray = new CTRow[ctRowList.size()];
  2872. ctRowArray = ctRowList.toArray(ctRowArray);
  2873. sheetData.setRowArray(ctRowArray);
  2874. //rebuild the _rows map
  2875. _rows.clear();
  2876. for (CTRow ctRow : sheetData.getRowList()) {
  2877. XSSFRow row = new XSSFRow(ctRow, this);
  2878. Integer rownumI = Math.toIntExact(row.getRowNum());
  2879. _rows.put(rownumI, row);
  2880. }
  2881. }
  2882. // remove all rows which will be overwritten
  2883. private void removeOverwritten(int startRow, int endRow, final int n) {
  2884. XSSFVMLDrawing vml = getVMLDrawing(false);
  2885. HashSet<Integer> rowsToRemoveSet = new HashSet<>();
  2886. for (Iterator<Row> it = rowIterator() ; it.hasNext() ; ) {
  2887. XSSFRow row = (XSSFRow)it.next();
  2888. int rownum = row.getRowNum();
  2889. // check if we should remove this row as it will be overwritten by the data later
  2890. if (shouldRemoveRow(startRow, endRow, n, rownum)) {
  2891. rowsToRemoveSet.add(rownum);
  2892. for (Cell c : row) {
  2893. if (!c.isPartOfArrayFormulaGroup()) {
  2894. //the support for deleting cells that are part of array formulas is not implemented yet
  2895. c.setBlank();
  2896. }
  2897. }
  2898. // remove row from worksheet.getSheetData row array
  2899. // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory
  2900. //noinspection UnnecessaryBoxing
  2901. final Integer rownumI = Integer.valueOf(row.getRowNum()); // NOSONAR
  2902. int idx = _rows.headMap(rownumI).size();
  2903. worksheet.getSheetData().removeRow(idx);
  2904. // remove row from _rows
  2905. it.remove();
  2906. }
  2907. }
  2908. // also remove any comments associated with this row
  2909. if (sheetComments != null) {
  2910. ArrayList<CellAddress> refsToRemove = new ArrayList<>();
  2911. Iterator<CellAddress> commentAddressIterator = sheetComments.getCellAddresses();
  2912. while (commentAddressIterator.hasNext()) {
  2913. CellAddress ref = commentAddressIterator.next();
  2914. // is this comment part of the current row?
  2915. if(rowsToRemoveSet.contains(ref.getRow())) {
  2916. refsToRemove.add(ref);
  2917. }
  2918. }
  2919. for (CellAddress ref : refsToRemove) {
  2920. sheetComments.removeComment(ref);
  2921. if (vml != null) {
  2922. vml.removeCommentShape(ref.getRow(), ref.getColumn());
  2923. }
  2924. }
  2925. }
  2926. // also remove any hyperlinks associated with this row
  2927. if (hyperlinks != null) {
  2928. for (XSSFHyperlink link : new ArrayList<>(hyperlinks)) {
  2929. CellRangeAddress range = CellRangeAddress.valueOf(link.getCellRef());
  2930. if (range.getFirstRow() == range.getLastRow() && rowsToRemoveSet.contains(range.getFirstRow())) {
  2931. removeHyperlink(link);
  2932. } else if (range.getFirstRow() != range.getLastRow()) {
  2933. boolean toRemove = true;
  2934. for (int i = range.getFirstRow(); i <= range.getLastRow() && toRemove; i++) {
  2935. toRemove = rowsToRemoveSet.contains(i);
  2936. }
  2937. if (toRemove) {
  2938. removeHyperlink(link);
  2939. }
  2940. }
  2941. }
  2942. }
  2943. }
  2944. private void shiftCommentsAndRows(int startRow, int endRow, final int n) {
  2945. // then do the actual moving and also adjust comments/rowHeight
  2946. // we need to sort it in a way so the shifting does not mess up the structures,
  2947. // i.e. when shifting down, start from down and go up, when shifting up, vice-versa
  2948. SortedMap<XSSFComment, Integer> commentsToShift = new TreeMap<>((o1, o2) -> {
  2949. int row1 = o1.getRow();
  2950. int row2 = o2.getRow();
  2951. if (row1 == row2) {
  2952. // ordering is not important when row is equal, but don't return zero to still
  2953. // get multiple comments per row into the map
  2954. return o1.hashCode() - o2.hashCode();
  2955. }
  2956. // when shifting down, sort higher row-values first
  2957. if (n > 0) {
  2958. return row1 < row2 ? 1 : -1;
  2959. } else {
  2960. // sort lower-row values first when shifting up
  2961. return row1 > row2 ? 1 : -1;
  2962. }
  2963. });
  2964. for (Iterator<Row> it = rowIterator() ; it.hasNext() ; ) {
  2965. XSSFRow row = (XSSFRow)it.next();
  2966. int rownum = row.getRowNum();
  2967. if(sheetComments != null) {
  2968. // calculate the new rownum
  2969. int newrownum = shiftedRowNum(startRow, endRow, n, rownum);
  2970. // is there a change necessary for the current row?
  2971. if(newrownum != rownum) {
  2972. Iterator<CellAddress> commentAddressIterator = sheetComments.getCellAddresses();
  2973. while (commentAddressIterator.hasNext()) {
  2974. CellAddress cellAddress = commentAddressIterator.next();
  2975. // is this comment part of the current row?
  2976. if(cellAddress.getRow() == rownum) {
  2977. XSSFComment oldComment = sheetComments.findCellComment(cellAddress);
  2978. if (oldComment != null) {
  2979. XSSFComment xssfComment = new XSSFComment(sheetComments, oldComment.getCTComment(),
  2980. oldComment.getCTShape());
  2981. // we should not perform the shifting right here as we would then find
  2982. // already shifted comments and would shift them again...
  2983. commentsToShift.put(xssfComment, newrownum);
  2984. }
  2985. }
  2986. }
  2987. }
  2988. }
  2989. if(rownum < startRow || rownum > endRow) {
  2990. continue;
  2991. }
  2992. row.shift(n);
  2993. }
  2994. // adjust all the affected comment-structures now
  2995. // the Map is sorted and thus provides them in the order that we need here,
  2996. // i.e. from down to up if shifting down, vice-versa otherwise
  2997. for(Map.Entry<XSSFComment, Integer> entry : commentsToShift.entrySet()) {
  2998. entry.getKey().setRow(entry.getValue());
  2999. }
  3000. rebuildRows();
  3001. }
  3002. private int shiftedRowNum(int startRow, int endRow, int n, int rownum) {
  3003. // no change if before any affected row
  3004. if(rownum < startRow && (n > 0 || (startRow - rownum) > n)) {
  3005. return rownum;
  3006. }
  3007. // no change if after any affected row
  3008. if(rownum > endRow && (n < 0 || (rownum - endRow) > n)) {
  3009. return rownum;
  3010. }
  3011. // row before and things are moved up
  3012. if(rownum < startRow) {
  3013. // row is moved down by the shifting
  3014. return rownum + (endRow - startRow);
  3015. }
  3016. // row is after and things are moved down
  3017. if(rownum > endRow) {
  3018. // row is moved up by the shifting
  3019. return rownum - (endRow - startRow);
  3020. }
  3021. // row is part of the shifted block
  3022. return rownum + n;
  3023. }
  3024. private void shiftCommentsForColumns(XSSFVMLDrawing vml, int startColumnIndex, int endColumnIndex, final int n){
  3025. // then do the actual moving and also adjust comments/rowHeight
  3026. // we need to sort it in a way so the shifting does not mess up the structures,
  3027. // i.e. when shifting down, start from down and go up, when shifting up, vice-versa
  3028. SortedMap<XSSFComment, Integer> commentsToShift = new TreeMap<>((o1, o2) -> {
  3029. int column1 = o1.getColumn();
  3030. int column2 = o2.getColumn();
  3031. if (column1 == column2) {
  3032. // ordering is not important when row is equal, but don't return zero to still
  3033. // get multiple comments per row into the map
  3034. return o1.hashCode() - o2.hashCode();
  3035. }
  3036. // when shifting down, sort higher row-values first
  3037. if (n > 0) {
  3038. return column1 < column2 ? 1 : -1;
  3039. } else {
  3040. // sort lower-row values first when shifting up
  3041. return column1 > column2 ? 1 : -1;
  3042. }
  3043. });
  3044. if (sheetComments != null) {
  3045. Iterator<CellAddress> commentAddressIterator = sheetComments.getCellAddresses();
  3046. while (commentAddressIterator.hasNext()) {
  3047. CellAddress oldCommentAddress = commentAddressIterator.next();
  3048. int columnIndex = oldCommentAddress.getColumn();
  3049. int newColumnIndex = shiftedRowNum(startColumnIndex, endColumnIndex, n, columnIndex);
  3050. if(newColumnIndex != columnIndex) {
  3051. XSSFComment oldComment = sheetComments.findCellComment(oldCommentAddress);
  3052. if (oldComment != null) {
  3053. XSSFComment xssfComment = new XSSFComment(sheetComments, oldComment.getCTComment(),
  3054. oldComment.getCTShape());
  3055. commentsToShift.put(xssfComment, newColumnIndex);
  3056. }
  3057. }
  3058. }
  3059. }
  3060. // adjust all the affected comment-structures now
  3061. // the Map is sorted and thus provides them in the order that we need here,
  3062. // i.e. from down to up if shifting down, vice-versa otherwise
  3063. for(Map.Entry<XSSFComment, Integer> entry : commentsToShift.entrySet()) {
  3064. entry.getKey().setColumn(entry.getValue());
  3065. }
  3066. rebuildRows();
  3067. }
  3068. /**
  3069. * Location of the top left visible cell Location of the top left visible cell in the bottom right
  3070. * pane (when in Left-to-Right mode).
  3071. *
  3072. * @param topRow the top row to show in desktop window pane
  3073. * @param leftCol the left column to show in desktop window pane
  3074. */
  3075. @Override
  3076. public void showInPane(int topRow, int leftCol) {
  3077. final CellReference cellReference = new CellReference(topRow, leftCol);
  3078. final String cellRef = cellReference.formatAsString();
  3079. final CTPane pane = getPane(true);
  3080. assert(pane != null);
  3081. pane.setTopLeftCell(cellRef);
  3082. }
  3083. @Override
  3084. public void ungroupColumn(int fromColumn, int toColumn) {
  3085. CTCols cols = worksheet.getColsArray(0);
  3086. int maxLevelCol = -1;
  3087. for (int index = fromColumn; index <= toColumn; index++) {
  3088. CTCol col = columnHelper.getColumn(index, false);
  3089. if (col != null) {
  3090. final short outlineLevel = col.getOutlineLevel();
  3091. maxLevelCol = Math.max(maxLevelCol, outlineLevel);
  3092. col.setOutlineLevel((short) (outlineLevel - 1));
  3093. index = Math.toIntExact(col.getMax());
  3094. if (col.getOutlineLevel() <= 0) {
  3095. int colIndex = columnHelper.getIndexOfColumn(cols, col);
  3096. worksheet.getColsArray(0).removeCol(colIndex);
  3097. }
  3098. }
  3099. }
  3100. worksheet.setColsArray(0, cols);
  3101. if (maxLevelCol >= getSheetFormatPrOutlineLevelCol()) {
  3102. setSheetFormatPrOutlineLevelCol();
  3103. }
  3104. }
  3105. /**
  3106. * Ungroup a range of rows that were previously grouped
  3107. *
  3108. * @param fromRow start row (0-based)
  3109. * @param toRow end row (0-based)
  3110. */
  3111. @Override
  3112. public void ungroupRow(int fromRow, int toRow) {
  3113. int maxOutlineLevel = -1;
  3114. for (int i = fromRow; i <= toRow; i++) {
  3115. XSSFRow xrow = getRow(i);
  3116. if (xrow != null) {
  3117. CTRow ctRow = xrow.getCTRow();
  3118. final short outlineLevel = ctRow.getOutlineLevel();
  3119. ctRow.setOutlineLevel((short) (outlineLevel - 1));
  3120. maxOutlineLevel = Math.max(maxOutlineLevel, outlineLevel);
  3121. //remove a row only if the row has no cell and if the outline level is 0
  3122. if (outlineLevel == 1 && xrow.getFirstCellNum() == -1) {
  3123. removeRow(xrow);
  3124. }
  3125. }
  3126. }
  3127. if (maxOutlineLevel >= getSheetFormatPrOutlineLevelRow()) {
  3128. setSheetFormatPrOutlineLevelRow();
  3129. }
  3130. }
  3131. private void increaseSheetFormatPrOutlineLevelRowIfNecessary(final short levelRow) {
  3132. if (levelRow > getSheetFormatPrOutlineLevelRow()) {
  3133. getSheetTypeSheetFormatPr(true).setOutlineLevelRow(levelRow);
  3134. }
  3135. }
  3136. private void increaseSheetFormatPrOutlineLevelColIfNecessary(final short levelCol) {
  3137. if (levelCol > getSheetFormatPrOutlineLevelCol()) {
  3138. getSheetTypeSheetFormatPr(true).setOutlineLevelCol(levelCol);
  3139. }
  3140. }
  3141. private void setSheetFormatPrOutlineLevelRow() {
  3142. final short maxLevelRow = getMaxOutlineLevelRows();
  3143. getSheetTypeSheetFormatPr(true).setOutlineLevelRow(maxLevelRow);
  3144. }
  3145. @Internal // used by SXSSFSHeet
  3146. public short getSheetFormatPrOutlineLevelRow() {
  3147. final CTSheetFormatPr pr = getSheetTypeSheetFormatPr(false);
  3148. return pr == null ? 0 : pr.getOutlineLevelRow();
  3149. }
  3150. @Internal // used by SXSSFSHeet
  3151. public short getSheetFormatPrOutlineLevelCol() {
  3152. final CTSheetFormatPr pr = getSheetTypeSheetFormatPr(false);
  3153. return pr == null ? 0 : pr.getOutlineLevelCol();
  3154. }
  3155. private void setSheetFormatPrOutlineLevelCol() {
  3156. final short maxLevelCol = getMaxOutlineLevelCols();
  3157. getSheetTypeSheetFormatPr(true).setOutlineLevelCol(maxLevelCol);
  3158. }
  3159. protected CTSheetViews getSheetTypeSheetViews(final boolean create) {
  3160. final CTSheetViews views = (worksheet.isSetSheetViews() || !create)
  3161. ? worksheet.getSheetViews() : worksheet.addNewSheetViews();
  3162. assert(views != null || !create);
  3163. if (views == null) {
  3164. return null;
  3165. }
  3166. if (views.sizeOfSheetViewArray() == 0 && create) {
  3167. views.addNewSheetView();
  3168. }
  3169. return views;
  3170. }
  3171. /**
  3172. * Returns a flag indicating whether this sheet is selected.
  3173. * <p>
  3174. * When only 1 sheet is selected and active, this value should be in synch with the activeTab value.
  3175. * In case of a conflict, the Start Part setting wins and sets the active sheet tab.
  3176. * </p>
  3177. * Note: multiple sheets can be selected, but only one sheet can be active at one time.
  3178. *
  3179. * @return {@code true} if this sheet is selected
  3180. */
  3181. @Override
  3182. public boolean isSelected() {
  3183. final CTSheetView dsv = getDefaultSheetView(false);
  3184. return dsv != null && dsv.getTabSelected();
  3185. }
  3186. /**
  3187. * Sets a flag indicating whether this sheet is selected.
  3188. *
  3189. * <p>
  3190. * When only 1 sheet is selected and active, this value should be in synch with the activeTab value.
  3191. * In case of a conflict, the Start Part setting wins and sets the active sheet tab.
  3192. * </p>
  3193. * Note: multiple sheets can be selected, but only one sheet can be active at one time.
  3194. *
  3195. * @param value {@code true} if this sheet is selected
  3196. */
  3197. @Override
  3198. public void setSelected(boolean value) {
  3199. final CTSheetViews views = getSheetTypeSheetViews(true);
  3200. assert(views != null);
  3201. for (CTSheetView view : views.getSheetViewArray()) {
  3202. view.setTabSelected(value);
  3203. }
  3204. }
  3205. /**
  3206. * Register a hyperlink in the collection of hyperlinks on this sheet.
  3207. * Use {@link XSSFCell#setHyperlink(Hyperlink)} if the hyperlink is just for that one cell.
  3208. * Use this method if you want to add a Hyperlink that covers a range of sells. If you use
  3209. * this method, you will need to call {@link XSSFHyperlink#setCellReference(String)} to
  3210. * explicitly cell the value, eg B2 or B2:C3 (the 4 cells with B2 at top left and C3 at bottom right)
  3211. *
  3212. * @param hyperlink the link to add
  3213. */
  3214. public void addHyperlink(XSSFHyperlink hyperlink) {
  3215. hyperlinks.add(hyperlink);
  3216. }
  3217. /**
  3218. * Remove a hyperlink in the collection of hyperlinks on this sheet.
  3219. * {@link XSSFCell#removeHyperlink()} can be used if the hyperlink is just for that one cell.
  3220. *
  3221. * @param hyperlink the link to remove
  3222. * @since POI 5.1.0
  3223. */
  3224. public void removeHyperlink(XSSFHyperlink hyperlink) {
  3225. hyperlinks.remove(hyperlink);
  3226. }
  3227. /**
  3228. * Removes a hyperlink in the collection of hyperlinks on this sheet
  3229. *
  3230. * @param row row index
  3231. * @param column column index
  3232. */
  3233. @Internal
  3234. public void removeHyperlink(int row, int column) {
  3235. // CTHyperlinks is regenerated from scratch when writing out the spreadsheet
  3236. // so don't worry about maintaining hyperlinks and CTHyperlinks in parallel.
  3237. // only maintain hyperlinks
  3238. XSSFHyperlink hyperlink = getHyperlink(row, column);
  3239. if (hyperlink != null) {
  3240. if (hyperlink.getFirstRow() == row && hyperlink.getLastRow() == row
  3241. && hyperlink.getFirstColumn() == column && hyperlink.getLastColumn() == column) {
  3242. removeHyperlink(hyperlink);
  3243. } else {
  3244. //we have a cellRef that spans multiple cells - we just want to remove the hyperlink from one cell
  3245. //we delete this hyperlink but add new hyperlinks to cover the other cells that were served
  3246. //by the old hyperlink
  3247. boolean leftCreated = false;
  3248. boolean rightCreated = false;
  3249. if (hyperlink.getFirstColumn() < column) {
  3250. XSSFHyperlink newLink = new XSSFHyperlink(hyperlink);
  3251. newLink.setFirstColumn(hyperlink.getFirstColumn());
  3252. newLink.setLastColumn(column - 1);
  3253. newLink.setFirstRow(hyperlink.getFirstRow());
  3254. newLink.setLastRow(hyperlink.getLastRow());
  3255. addHyperlink(newLink);
  3256. leftCreated = true;
  3257. }
  3258. if (hyperlink.getLastColumn() > column) {
  3259. XSSFHyperlink newLink = new XSSFHyperlink(hyperlink);
  3260. newLink.setFirstColumn(column + 1);
  3261. newLink.setLastColumn(hyperlink.getLastColumn());
  3262. newLink.setFirstRow(hyperlink.getFirstRow());
  3263. newLink.setLastRow(hyperlink.getLastRow());
  3264. addHyperlink(newLink);
  3265. rightCreated = true;
  3266. }
  3267. if (hyperlink.getFirstRow() < row) {
  3268. XSSFHyperlink newLink = new XSSFHyperlink(hyperlink);
  3269. int firstColumn = leftCreated ? row : hyperlink.getFirstColumn();
  3270. int lastColumn = rightCreated ? row : hyperlink.getLastColumn();
  3271. newLink.setFirstColumn(firstColumn);
  3272. newLink.setLastColumn(lastColumn);
  3273. newLink.setFirstRow(hyperlink.getFirstRow());
  3274. newLink.setLastRow(row - 1);
  3275. addHyperlink(newLink);
  3276. }
  3277. if (hyperlink.getLastRow() > row) {
  3278. XSSFHyperlink newLink = new XSSFHyperlink(hyperlink);
  3279. int firstColumn = leftCreated ? row : hyperlink.getFirstColumn();
  3280. int lastColumn = rightCreated ? row : hyperlink.getLastColumn();
  3281. newLink.setFirstColumn(firstColumn);
  3282. newLink.setLastColumn(lastColumn);
  3283. newLink.setFirstRow(row + 1);
  3284. newLink.setLastRow(hyperlink.getLastRow());
  3285. addHyperlink(newLink);
  3286. }
  3287. removeHyperlink(hyperlink);
  3288. }
  3289. }
  3290. }
  3291. /**
  3292. * Return location of the active cell, e.g. {@code A1}.
  3293. *
  3294. * @return the location of the active cell.
  3295. */
  3296. @Override
  3297. public CellAddress getActiveCell() {
  3298. final CTSelection sts = getSheetTypeSelection(false);
  3299. final String address = (sts != null) ? sts.getActiveCell() : null;
  3300. return (address != null) ? new CellAddress(address) : null;
  3301. }
  3302. /**
  3303. * {@inheritDoc}
  3304. */
  3305. @Override
  3306. public void setActiveCell(CellAddress address) {
  3307. final CTSelection ctsel = getSheetTypeSelection(true);
  3308. assert(ctsel != null);
  3309. String ref = address.formatAsString();
  3310. ctsel.setActiveCell(ref);
  3311. ctsel.setSqref(Collections.singletonList(ref));
  3312. }
  3313. /**
  3314. * Does this sheet have any comments on it? We need to know,
  3315. * so we can decide about writing it to disk or not
  3316. */
  3317. public boolean hasComments() {
  3318. return sheetComments != null && sheetComments.getNumberOfComments() > 0;
  3319. }
  3320. protected int getNumberOfComments() {
  3321. return sheetComments == null ? 0 : sheetComments.getNumberOfComments();
  3322. }
  3323. private CTSelection getSheetTypeSelection(final boolean create) {
  3324. final CTSheetView dsv = getDefaultSheetView(create);
  3325. assert(dsv != null || !create);
  3326. if (dsv == null) {
  3327. return null;
  3328. }
  3329. final int sz = dsv.sizeOfSelectionArray();
  3330. if (sz == 0) {
  3331. return create ? dsv.addNewSelection() : null;
  3332. }
  3333. return dsv.getSelectionArray(sz - 1);
  3334. }
  3335. /**
  3336. * Return the default sheet view. This is the last one if the sheet's views, according to sec. 3.3.1.83
  3337. * of the OOXML spec: "A single sheet view definition. When more than 1 sheet view is defined in the file,
  3338. * it means that when opening the workbook, each sheet view corresponds to a separate window within the
  3339. * spreadsheet application, where each window is showing the particular sheet. containing the same
  3340. * workbookViewId value, the last sheetView definition is loaded, and the others are discarded.
  3341. * When multiple windows are viewing the same sheet, multiple sheetView elements (with corresponding
  3342. * workbookView entries) are saved."
  3343. */
  3344. private CTSheetView getDefaultSheetView(final boolean create) {
  3345. final CTSheetViews views = getSheetTypeSheetViews(create);
  3346. assert(views != null || !create);
  3347. if (views == null) {
  3348. return null;
  3349. }
  3350. final int sz = views.sizeOfSheetViewArray();
  3351. assert(sz > 0 || !create);
  3352. return (sz == 0) ? null : views.getSheetViewArray(sz - 1);
  3353. }
  3354. /**
  3355. * Returns the sheet's comments object if there is one,
  3356. * or null if not
  3357. *
  3358. * @param create create a new comments table if it does not exist
  3359. */
  3360. protected Comments getCommentsTable(boolean create) {
  3361. if(sheetComments == null && create){
  3362. // Try to create a comments table with the same number as
  3363. // the sheet has (i.e. sheet 1 -> comments 1)
  3364. try {
  3365. sheetComments = (Comments)createRelationship(
  3366. XSSFRelation.SHEET_COMMENTS, getWorkbook().getXssfFactory(), Math.toIntExact(sheet.getSheetId()));
  3367. } catch(PartAlreadyExistsException e) {
  3368. // Technically a sheet doesn't need the same number as
  3369. // its comments, and clearly someone has already pinched
  3370. // our number! Go for the next available one instead
  3371. sheetComments = (Comments)createRelationship(
  3372. XSSFRelation.SHEET_COMMENTS, getWorkbook().getXssfFactory(), -1);
  3373. }
  3374. if (sheetComments != null) {
  3375. sheetComments.setSheet(this);
  3376. }
  3377. }
  3378. return sheetComments;
  3379. }
  3380. private CTPageSetUpPr getSheetTypePageSetUpPr() {
  3381. CTSheetPr sheetPr = getSheetTypeSheetPr();
  3382. return sheetPr.isSetPageSetUpPr() ? sheetPr.getPageSetUpPr() : sheetPr.addNewPageSetUpPr();
  3383. }
  3384. private static boolean shouldRemoveRow(int startRow, int endRow, int n, int rownum) {
  3385. // is this row in the target-window where the moved rows will land?
  3386. if (rownum >= (startRow + n) && rownum <= (endRow + n)) {
  3387. // only remove it if the current row is not part of the data that is copied
  3388. if (n > 0 && rownum > endRow) {
  3389. return true;
  3390. }
  3391. else {
  3392. return n < 0 && rownum < startRow;
  3393. }
  3394. }
  3395. return false;
  3396. }
  3397. private CTPane getPane(final boolean create) {
  3398. final CTSheetView dsv = getDefaultSheetView(create);
  3399. assert(dsv != null || !create);
  3400. if (dsv == null) {
  3401. return null;
  3402. }
  3403. return (dsv.isSetPane() || !create) ? dsv.getPane() : dsv.addNewPane();
  3404. }
  3405. /**
  3406. * Return a master shared formula by index
  3407. *
  3408. * @param sid shared group index
  3409. * @return a CTCellFormula bean holding shared formula or {@code null} if not found
  3410. */
  3411. @Internal
  3412. public CTCellFormula getSharedFormula(int sid){
  3413. return sharedFormulas.get(sid);
  3414. }
  3415. void onReadCell(XSSFCell cell){
  3416. //collect cells holding shared formulas
  3417. CTCell ct = cell.getCTCell();
  3418. CTCellFormula f = ct.getF();
  3419. if (f != null && f.getT() == STCellFormulaType.SHARED && f.isSetRef() && f.getStringValue() != null) {
  3420. // save a detached copy to avoid XmlValueDisconnectedException,
  3421. // this may happen when the master cell of a shared formula is changed
  3422. CTCellFormula sf = (CTCellFormula)f.copy();
  3423. CellRangeAddress sfRef = CellRangeAddress.valueOf(sf.getRef());
  3424. CellReference cellRef = new CellReference(cell);
  3425. // If the shared formula range precedes the master cell then the preceding part is discarded, e.g.
  3426. // if the cell is E60 and the shared formula range is C60:M85 then the effective range is E60:M85
  3427. // see more details in https://issues.apache.org/bugzilla/show_bug.cgi?id=51710
  3428. if(cellRef.getCol() > sfRef.getFirstColumn() || cellRef.getRow() > sfRef.getFirstRow()){
  3429. String effectiveRef = new CellRangeAddress(
  3430. Math.max(cellRef.getRow(), sfRef.getFirstRow()), Math.max(cellRef.getRow(), sfRef.getLastRow()),
  3431. Math.max(cellRef.getCol(), sfRef.getFirstColumn()), Math.max(cellRef.getCol(), sfRef.getLastColumn()))
  3432. .formatAsString();
  3433. sf.setRef(effectiveRef);
  3434. }
  3435. sharedFormulas.put(Math.toIntExact(f.getSi()), sf);
  3436. }
  3437. if (f != null && f.getT() == STCellFormulaType.ARRAY && f.getRef() != null) {
  3438. arrayFormulas.add(CellRangeAddress.valueOf(f.getRef()));
  3439. }
  3440. }
  3441. @Override
  3442. protected void commit() throws IOException {
  3443. PackagePart part = getPackagePart();
  3444. try (OutputStream out = part.getOutputStream()) {
  3445. write(out);
  3446. }
  3447. }
  3448. protected void write(OutputStream out) throws IOException {
  3449. if (worksheet == null) {
  3450. throw new POIXMLException("Cannot write invalid sheet, internal data is missing");
  3451. }
  3452. boolean setToNull = false;
  3453. if(worksheet.sizeOfColsArray() == 1) {
  3454. CTCols col = worksheet.getColsArray(0);
  3455. if(col.sizeOfColArray() == 0) {
  3456. setToNull = true;
  3457. // this is necessary so that we do not write an empty <cols/> item into the sheet-xml in the xlsx-file
  3458. // Excel complains about a corrupted file if this shows up there!
  3459. worksheet.setColsArray(null);
  3460. } else {
  3461. setColWidthAttribute(col);
  3462. }
  3463. }
  3464. // Now re-generate our CTHyperlinks, if needed
  3465. if (!hyperlinks.isEmpty()) {
  3466. if(worksheet.getHyperlinks() == null) {
  3467. worksheet.addNewHyperlinks();
  3468. }
  3469. CTHyperlink[] ctHls = new CTHyperlink[hyperlinks.size()];
  3470. for(int i=0; i<ctHls.length; i++) {
  3471. // If our sheet has hyperlinks, have them add
  3472. // any relationships that they might need
  3473. XSSFHyperlink hyperlink = hyperlinks.get(i);
  3474. hyperlink.generateRelationIfNeeded(getPackagePart());
  3475. // Now grab their underling object
  3476. ctHls[i] = hyperlink.getCTHyperlink();
  3477. }
  3478. worksheet.getHyperlinks().setHyperlinkArray(ctHls);
  3479. }
  3480. else {
  3481. if (worksheet.getHyperlinks() != null) {
  3482. final int count = worksheet.getHyperlinks().sizeOfHyperlinkArray();
  3483. for (int i=count-1; i>=0; i--) {
  3484. worksheet.getHyperlinks().removeHyperlink(i);
  3485. }
  3486. // For some reason, we have to remove the hyperlinks one by one from the CTHyperlinks array
  3487. // before unsetting the hyperlink array.
  3488. // Resetting the hyperlink array seems to break some XML nodes.
  3489. //worksheet.getHyperlinks().setHyperlinkArray(new CTHyperlink[0]);
  3490. worksheet.unsetHyperlinks();
  3491. } /*else {
  3492. // nothing to do
  3493. }*/
  3494. }
  3495. CellRangeAddress cellRangeAddress = dimensionOverride;
  3496. if (cellRangeAddress == null) {
  3497. int minCell = Integer.MAX_VALUE, maxCell = Integer.MIN_VALUE;
  3498. for(Map.Entry<Integer, XSSFRow> entry : _rows.entrySet()) {
  3499. XSSFRow row = entry.getValue();
  3500. // first perform the normal write actions for the row
  3501. row.onDocumentWrite();
  3502. // then calculate min/max cell-numbers for the worksheet-dimension
  3503. if(row.getFirstCellNum() != -1) {
  3504. minCell = Math.min(minCell, row.getFirstCellNum());
  3505. }
  3506. if(row.getLastCellNum() != -1) {
  3507. maxCell = Math.max(maxCell, row.getLastCellNum()-1);
  3508. }
  3509. }
  3510. // finally, if we had at least one cell we can populate the optional dimension-field
  3511. if(minCell != Integer.MAX_VALUE) {
  3512. cellRangeAddress = new CellRangeAddress(getFirstRowNum(), getLastRowNum(), minCell, maxCell);
  3513. }
  3514. }
  3515. if (cellRangeAddress != null) {
  3516. if (worksheet.isSetDimension()) {
  3517. worksheet.getDimension().setRef(cellRangeAddress.formatAsString());
  3518. } else {
  3519. worksheet.addNewDimension().setRef(cellRangeAddress.formatAsString());
  3520. }
  3521. }
  3522. XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
  3523. xmlOptions.setSaveSyntheticDocumentElement(new QName(CTWorksheet.type.getName().getNamespaceURI(), "worksheet"));
  3524. worksheet.save(out, xmlOptions);
  3525. // Bug 52233: Ensure that we have a col-array even if write() removed it
  3526. if(setToNull) {
  3527. worksheet.addNewCols();
  3528. }
  3529. }
  3530. /**
  3531. * @return true when Autofilters are locked and the sheet is protected.
  3532. */
  3533. public boolean isAutoFilterLocked() {
  3534. return isSheetLocked() && safeGetProtectionField().getAutoFilter();
  3535. }
  3536. /**
  3537. * @return true when Deleting columns is locked and the sheet is protected.
  3538. */
  3539. public boolean isDeleteColumnsLocked() {
  3540. return isSheetLocked() && safeGetProtectionField().getDeleteColumns();
  3541. }
  3542. /**
  3543. * @return true when Deleting rows is locked and the sheet is protected.
  3544. */
  3545. public boolean isDeleteRowsLocked() {
  3546. return isSheetLocked() && safeGetProtectionField().getDeleteRows();
  3547. }
  3548. /**
  3549. * @return true when Formatting cells is locked and the sheet is protected.
  3550. */
  3551. public boolean isFormatCellsLocked() {
  3552. return isSheetLocked() && safeGetProtectionField().getFormatCells();
  3553. }
  3554. /**
  3555. * @return true when Formatting columns is locked and the sheet is protected.
  3556. */
  3557. public boolean isFormatColumnsLocked() {
  3558. return isSheetLocked() && safeGetProtectionField().getFormatColumns();
  3559. }
  3560. /**
  3561. * @return true when Formatting rows is locked and the sheet is protected.
  3562. */
  3563. public boolean isFormatRowsLocked() {
  3564. return isSheetLocked() && safeGetProtectionField().getFormatRows();
  3565. }
  3566. /**
  3567. * @return true when Inserting columns is locked and the sheet is protected.
  3568. */
  3569. public boolean isInsertColumnsLocked() {
  3570. return isSheetLocked() && safeGetProtectionField().getInsertColumns();
  3571. }
  3572. /**
  3573. * @return true when Inserting hyperlinks is locked and the sheet is protected.
  3574. */
  3575. public boolean isInsertHyperlinksLocked() {
  3576. return isSheetLocked() && safeGetProtectionField().getInsertHyperlinks();
  3577. }
  3578. /**
  3579. * @return true when Inserting rows is locked and the sheet is protected.
  3580. */
  3581. public boolean isInsertRowsLocked() {
  3582. return isSheetLocked() && safeGetProtectionField().getInsertRows();
  3583. }
  3584. /**
  3585. * @return true when Pivot tables are locked and the sheet is protected.
  3586. */
  3587. public boolean isPivotTablesLocked() {
  3588. return isSheetLocked() && safeGetProtectionField().getPivotTables();
  3589. }
  3590. /**
  3591. * @return true when Sorting is locked and the sheet is protected.
  3592. */
  3593. public boolean isSortLocked() {
  3594. return isSheetLocked() && safeGetProtectionField().getSort();
  3595. }
  3596. /**
  3597. * @return true when Objects are locked and the sheet is protected.
  3598. */
  3599. public boolean isObjectsLocked() {
  3600. return isSheetLocked() && safeGetProtectionField().getObjects();
  3601. }
  3602. /**
  3603. * @return true when Scenarios are locked and the sheet is protected.
  3604. */
  3605. public boolean isScenariosLocked() {
  3606. return isSheetLocked() && safeGetProtectionField().getScenarios();
  3607. }
  3608. /**
  3609. * @return true when Selection of locked cells is locked and the sheet is protected.
  3610. */
  3611. public boolean isSelectLockedCellsLocked() {
  3612. return isSheetLocked() && safeGetProtectionField().getSelectLockedCells();
  3613. }
  3614. /**
  3615. * @return true when Selection of unlocked cells is locked and the sheet is protected.
  3616. */
  3617. public boolean isSelectUnlockedCellsLocked() {
  3618. return isSheetLocked() && safeGetProtectionField().getSelectUnlockedCells();
  3619. }
  3620. /**
  3621. * @return true when Sheet is Protected.
  3622. */
  3623. public boolean isSheetLocked() {
  3624. return worksheet.isSetSheetProtection() && safeGetProtectionField().getSheet();
  3625. }
  3626. /**
  3627. * Enable sheet protection
  3628. */
  3629. public void enableLocking() {
  3630. safeGetProtectionField().setSheet(true);
  3631. }
  3632. /**
  3633. * Disable sheet protection
  3634. */
  3635. public void disableLocking() {
  3636. safeGetProtectionField().setSheet(false);
  3637. }
  3638. /**
  3639. * Enable or disable Autofilters locking.
  3640. * This does not modify sheet protection status.
  3641. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3642. */
  3643. public void lockAutoFilter(boolean enabled) {
  3644. safeGetProtectionField().setAutoFilter(enabled);
  3645. }
  3646. /**
  3647. * Enable or disable Deleting columns locking.
  3648. * This does not modify sheet protection status.
  3649. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3650. */
  3651. public void lockDeleteColumns(boolean enabled) {
  3652. safeGetProtectionField().setDeleteColumns(enabled);
  3653. }
  3654. /**
  3655. * Enable or disable Deleting rows locking.
  3656. * This does not modify sheet protection status.
  3657. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3658. */
  3659. public void lockDeleteRows(boolean enabled) {
  3660. safeGetProtectionField().setDeleteRows(enabled);
  3661. }
  3662. /**
  3663. * Enable or disable Formatting cells locking.
  3664. * This does not modify sheet protection status.
  3665. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3666. */
  3667. public void lockFormatCells(boolean enabled) {
  3668. safeGetProtectionField().setFormatCells(enabled);
  3669. }
  3670. /**
  3671. * Enable or disable Formatting columns locking.
  3672. * This does not modify sheet protection status.
  3673. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3674. */
  3675. public void lockFormatColumns(boolean enabled) {
  3676. safeGetProtectionField().setFormatColumns(enabled);
  3677. }
  3678. /**
  3679. * Enable or disable Formatting rows locking.
  3680. * This does not modify sheet protection status.
  3681. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3682. */
  3683. public void lockFormatRows(boolean enabled) {
  3684. safeGetProtectionField().setFormatRows(enabled);
  3685. }
  3686. /**
  3687. * Enable or disable Inserting columns locking.
  3688. * This does not modify sheet protection status.
  3689. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3690. */
  3691. public void lockInsertColumns(boolean enabled) {
  3692. safeGetProtectionField().setInsertColumns(enabled);
  3693. }
  3694. /**
  3695. * Enable or disable Inserting hyperlinks locking.
  3696. * This does not modify sheet protection status.
  3697. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3698. */
  3699. public void lockInsertHyperlinks(boolean enabled) {
  3700. safeGetProtectionField().setInsertHyperlinks(enabled);
  3701. }
  3702. /**
  3703. * Enable or disable Inserting rows locking.
  3704. * This does not modify sheet protection status.
  3705. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3706. */
  3707. public void lockInsertRows(boolean enabled) {
  3708. safeGetProtectionField().setInsertRows(enabled);
  3709. }
  3710. /**
  3711. * Enable or disable Pivot Tables locking.
  3712. * This does not modify sheet protection status.
  3713. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3714. */
  3715. public void lockPivotTables(boolean enabled) {
  3716. safeGetProtectionField().setPivotTables(enabled);
  3717. }
  3718. /**
  3719. * Enable or disable Sort locking.
  3720. * This does not modify sheet protection status.
  3721. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3722. */
  3723. public void lockSort(boolean enabled) {
  3724. safeGetProtectionField().setSort(enabled);
  3725. }
  3726. /**
  3727. * Enable or disable Objects locking.
  3728. * This does not modify sheet protection status.
  3729. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3730. */
  3731. public void lockObjects(boolean enabled) {
  3732. safeGetProtectionField().setObjects(enabled);
  3733. }
  3734. /**
  3735. * Enable or disable Scenarios locking.
  3736. * This does not modify sheet protection status.
  3737. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3738. */
  3739. public void lockScenarios(boolean enabled) {
  3740. safeGetProtectionField().setScenarios(enabled);
  3741. }
  3742. /**
  3743. * Enable or disable Selection of locked cells locking.
  3744. * This does not modify sheet protection status.
  3745. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3746. */
  3747. public void lockSelectLockedCells(boolean enabled) {
  3748. safeGetProtectionField().setSelectLockedCells(enabled);
  3749. }
  3750. /**
  3751. * Enable or disable Selection of unlocked cells locking.
  3752. * This does not modify sheet protection status.
  3753. * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
  3754. */
  3755. public void lockSelectUnlockedCells(boolean enabled) {
  3756. safeGetProtectionField().setSelectUnlockedCells(enabled);
  3757. }
  3758. /**
  3759. * Reads the dimensions of the sheet data
  3760. * @return dimensions of the sheet data as a Cell Range (can be null)
  3761. * @since POI 5.2.3
  3762. */
  3763. public CellRangeAddress getDimension() {
  3764. if (dimensionOverride != null) {
  3765. return dimensionOverride;
  3766. }
  3767. CTSheetDimension ctSheetDimension = worksheet.getDimension();
  3768. String ref = ctSheetDimension == null ? null : ctSheetDimension.getRef();
  3769. if (ref != null) {
  3770. return CellRangeAddress.valueOf(ref);
  3771. }
  3772. return null;
  3773. }
  3774. private CTSheetProtection safeGetProtectionField() {
  3775. if (!isSheetProtectionEnabled()) {
  3776. return worksheet.addNewSheetProtection();
  3777. }
  3778. return worksheet.getSheetProtection();
  3779. }
  3780. /* package */ boolean isSheetProtectionEnabled() {
  3781. return (worksheet.isSetSheetProtection());
  3782. }
  3783. /* package */ boolean isCellInArrayFormulaContext(XSSFCell cell) {
  3784. final int rowIndex = cell.getRowIndex();
  3785. final int columnIndex = cell.getColumnIndex();
  3786. for (CellRangeAddress range : arrayFormulas) {
  3787. if (range.isInRange(rowIndex, columnIndex)) {
  3788. return true;
  3789. }
  3790. }
  3791. return false;
  3792. }
  3793. /* package */ XSSFCell getFirstCellInArrayFormula(XSSFCell cell) {
  3794. final int rowIndex = cell.getRowIndex();
  3795. final int columnIndex = cell.getColumnIndex();
  3796. for (CellRangeAddress range : arrayFormulas) {
  3797. if (range.isInRange(rowIndex, columnIndex)) {
  3798. return getRow(range.getFirstRow()).getCell(range.getFirstColumn());
  3799. }
  3800. }
  3801. return null;
  3802. }
  3803. /**
  3804. * Also creates cells if they don't exist
  3805. */
  3806. private CellRange<XSSFCell> getCellRange(CellRangeAddress range) {
  3807. int firstRow = range.getFirstRow();
  3808. int firstColumn = range.getFirstColumn();
  3809. int lastRow = range.getLastRow();
  3810. int lastColumn = range.getLastColumn();
  3811. int height = lastRow - firstRow + 1;
  3812. int width = lastColumn - firstColumn + 1;
  3813. List<XSSFCell> temp = new ArrayList<>(height * width);
  3814. for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) {
  3815. for (int colIn = firstColumn; colIn <= lastColumn; colIn++) {
  3816. XSSFRow row = getRow(rowIn);
  3817. if (row == null) {
  3818. row = createRow(rowIn);
  3819. }
  3820. XSSFCell cell = row.getCell(colIn);
  3821. if (cell == null) {
  3822. cell = row.createCell(colIn);
  3823. }
  3824. temp.add(cell);
  3825. }
  3826. }
  3827. return SSCellRange.create(firstRow, firstColumn, height, width, temp, XSSFCell.class);
  3828. }
  3829. @Override
  3830. public CellRange<XSSFCell> setArrayFormula(String formula, CellRangeAddress range) {
  3831. CellRange<XSSFCell> cr = getCellRange(range);
  3832. XSSFCell mainArrayFormulaCell = cr.getTopLeftCell();
  3833. mainArrayFormulaCell.setCellArrayFormula(formula, range);
  3834. arrayFormulas.add(range);
  3835. return cr;
  3836. }
  3837. @Override
  3838. public CellRange<XSSFCell> removeArrayFormula(Cell cell) {
  3839. if (cell.getSheet() != this) {
  3840. throw new IllegalArgumentException("Specified cell does not belong to this sheet.");
  3841. }
  3842. for (CellRangeAddress range : arrayFormulas) {
  3843. if (range.isInRange(cell)) {
  3844. arrayFormulas.remove(range);
  3845. CellRange<XSSFCell> cr = getCellRange(range);
  3846. for (XSSFCell c : cr) {
  3847. c.setBlank();
  3848. }
  3849. return cr;
  3850. }
  3851. }
  3852. String ref = new CellReference(cell).formatAsString();
  3853. throw new IllegalArgumentException("Cell " + ref + " is not part of an array formula.");
  3854. }
  3855. @Override
  3856. public DataValidationHelper getDataValidationHelper() {
  3857. return dataValidationHelper;
  3858. }
  3859. @Override
  3860. public List<XSSFDataValidation> getDataValidations() {
  3861. List<XSSFDataValidation> xssfValidations = new ArrayList<>();
  3862. CTDataValidations dataValidations = this.worksheet.getDataValidations();
  3863. if (dataValidations != null) {
  3864. for (CTDataValidation ctDataValidation : dataValidations.getDataValidationArray()) {
  3865. CellRangeAddressList addressList = new CellRangeAddressList();
  3866. @SuppressWarnings("unchecked")
  3867. List<String> sqref = ctDataValidation.getSqref();
  3868. for (String stRef : sqref) {
  3869. String[] regions = stRef.split(" ");
  3870. for (String region : regions) {
  3871. String[] parts = region.split(":");
  3872. CellReference begin = new CellReference(parts[0]);
  3873. CellReference end = parts.length > 1 ? new CellReference(parts[1]) : begin;
  3874. CellRangeAddress cellRangeAddress = new CellRangeAddress(begin.getRow(), end.getRow(), begin.getCol(), end.getCol());
  3875. addressList.addCellRangeAddress(cellRangeAddress);
  3876. }
  3877. }
  3878. XSSFDataValidation xssfDataValidation = new XSSFDataValidation(addressList, ctDataValidation);
  3879. xssfValidations.add(xssfDataValidation);
  3880. }
  3881. }
  3882. return xssfValidations;
  3883. }
  3884. @Override
  3885. public void addValidationData(DataValidation dataValidation) {
  3886. XSSFDataValidation xssfDataValidation = (XSSFDataValidation)dataValidation;
  3887. CTDataValidations dataValidations = worksheet.getDataValidations();
  3888. if( dataValidations==null ) {
  3889. dataValidations = worksheet.addNewDataValidations();
  3890. }
  3891. int currentCount = dataValidations.sizeOfDataValidationArray();
  3892. CTDataValidation newval = dataValidations.addNewDataValidation();
  3893. newval.set(xssfDataValidation.getCtDataValidation());
  3894. dataValidations.setCount(currentCount + 1L);
  3895. }
  3896. @Override
  3897. public XSSFAutoFilter setAutoFilter(CellRangeAddress range) {
  3898. CTAutoFilter af = worksheet.getAutoFilter();
  3899. if(af == null) {
  3900. af = worksheet.addNewAutoFilter();
  3901. }
  3902. CellRangeAddress norm = new CellRangeAddress(range.getFirstRow(), range.getLastRow(),
  3903. range.getFirstColumn(), range.getLastColumn());
  3904. String ref = norm.formatAsString();
  3905. af.setRef(ref);
  3906. XSSFWorkbook wb = getWorkbook();
  3907. int sheetIndex = getWorkbook().getSheetIndex(this);
  3908. XSSFName name = wb.getBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex);
  3909. if (name == null) {
  3910. name = wb.createBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex);
  3911. }
  3912. name.getCTName().setHidden(true);
  3913. CellReference r1 = new CellReference(getSheetName(), range.getFirstRow(), range.getFirstColumn(), true, true);
  3914. CellReference r2 = new CellReference(null, range.getLastRow(), range.getLastColumn(), true, true);
  3915. String fmla = r1.formatAsString() + ":" + r2.formatAsString();
  3916. name.setRefersToFormula(fmla);
  3917. return new XSSFAutoFilter(this);
  3918. }
  3919. /**
  3920. * Creates a new Table, and associates it with this Sheet.
  3921. * <p>
  3922. * The table is assigned a default display name (since 4.1.1) which can be overridden
  3923. * by calling {@code setDisplayName}. The default display name is guaranteed to not conflict
  3924. * with the names of any {@code XSSFName} or {@code XSSFTable} in the workbook.
  3925. *
  3926. * @param tableArea
  3927. * the area that the table should cover, should not be null
  3928. * @return the created table
  3929. * @since 4.0.0
  3930. */
  3931. public XSSFTable createTable(AreaReference tableArea) {
  3932. if (!worksheet.isSetTableParts()) {
  3933. worksheet.addNewTableParts();
  3934. }
  3935. CTTableParts tblParts = worksheet.getTableParts();
  3936. CTTablePart tbl = tblParts.addNewTablePart();
  3937. // Table numbers need to be unique in the file, not just
  3938. // unique within the sheet. Find the next one
  3939. int tableNumber = getPackagePart().getPackage().getPartsByContentType(XSSFRelation.TABLE.getContentType()).size() + 1;
  3940. // the id could already be taken after insertion/deletion of different tables
  3941. boolean loop = true;
  3942. while(loop) {
  3943. loop = false;
  3944. for (PackagePart packagePart : getPackagePart().getPackage().getPartsByContentType(XSSFRelation.TABLE.getContentType())) {
  3945. String fileName = XSSFRelation.TABLE.getFileName(tableNumber);
  3946. if(fileName.equals(packagePart.getPartName().getName())) {
  3947. // duplicate found, increase the number and start iterating again
  3948. tableNumber++;
  3949. loop = true;
  3950. }
  3951. }
  3952. }
  3953. RelationPart rp = createRelationship(XSSFRelation.TABLE, getWorkbook().getXssfFactory(), tableNumber, false);
  3954. XSSFTable table = rp.getDocumentPart();
  3955. tbl.setId(rp.getRelationship().getId());
  3956. table.getCTTable().setId(tableNumber);
  3957. tables.put(tbl.getId(), table);
  3958. if(tableArea != null && table.supportsAreaReference(tableArea)) {
  3959. table.setArea(tableArea);
  3960. }
  3961. // Set the default name of the table. This must not conflict with any defined names.
  3962. while(tableNumber<Integer.MAX_VALUE) {
  3963. final String displayName="Table"+tableNumber;
  3964. if(getWorkbook().getTable(displayName) == null &&
  3965. getWorkbook().getName(displayName) == null) {
  3966. table.setDisplayName(displayName);
  3967. table.setName(displayName);
  3968. break;
  3969. }
  3970. ++tableNumber;
  3971. }
  3972. return table;
  3973. }
  3974. /**
  3975. * Returns any tables associated with this Sheet
  3976. */
  3977. public List<XSSFTable> getTables() {
  3978. return new ArrayList<>(tables.values());
  3979. }
  3980. /**
  3981. * Remove table references and relations
  3982. * @param t table to remove
  3983. */
  3984. public void removeTable(XSSFTable t) {
  3985. String rId = getRelationId(t);
  3986. long id = t.getCTTable().getId();
  3987. Map.Entry<String, XSSFTable> toDelete = null;
  3988. for (Map.Entry<String, XSSFTable> entry : tables.entrySet()) {
  3989. if (entry.getValue().getCTTable().getId() == id) toDelete = entry;
  3990. }
  3991. if (toDelete != null) {
  3992. removeRelation(getRelationById(toDelete.getKey()), true);
  3993. tables.remove(toDelete.getKey());
  3994. toDelete.getValue().onTableDelete();
  3995. CTTableParts tblParts = worksheet.getTableParts();
  3996. int matchedPos = -1;
  3997. if (rId != null) {
  3998. for (int i = 0; i < tblParts.sizeOfTablePartArray(); i++) {
  3999. if (rId.equals(tblParts.getTablePartArray(i).getId())) {
  4000. matchedPos = i;
  4001. break;
  4002. }
  4003. }
  4004. }
  4005. if (matchedPos != -1) {
  4006. tblParts.removeTablePart(matchedPos);
  4007. }
  4008. }
  4009. }
  4010. @Override
  4011. public XSSFSheetConditionalFormatting getSheetConditionalFormatting(){
  4012. return new XSSFSheetConditionalFormatting(this);
  4013. }
  4014. /**
  4015. * Get background color of the sheet tab.
  4016. * Returns {@code null} if no sheet tab color is set.
  4017. *
  4018. * @return the background color of the sheet tab
  4019. */
  4020. public XSSFColor getTabColor() {
  4021. CTSheetPr pr = worksheet.getSheetPr();
  4022. if(pr == null) {
  4023. pr = worksheet.addNewSheetPr();
  4024. }
  4025. if (!pr.isSetTabColor()) {
  4026. return null;
  4027. }
  4028. return XSSFColor.from(pr.getTabColor(), getWorkbook().getStylesSource().getIndexedColors());
  4029. }
  4030. /**
  4031. * Set background color of the sheet tab
  4032. *
  4033. * @param color the color to set
  4034. */
  4035. public void setTabColor(XSSFColor color) {
  4036. CTSheetPr pr = worksheet.getSheetPr();
  4037. if(pr == null) {
  4038. pr = worksheet.addNewSheetPr();
  4039. }
  4040. pr.setTabColor(color.getCTColor());
  4041. }
  4042. @Override
  4043. public CellRangeAddress getRepeatingRows() {
  4044. return getRepeatingRowsOrColumns(true);
  4045. }
  4046. @Override
  4047. public CellRangeAddress getRepeatingColumns() {
  4048. return getRepeatingRowsOrColumns(false);
  4049. }
  4050. @Override
  4051. public void setRepeatingRows(CellRangeAddress rowRangeRef) {
  4052. CellRangeAddress columnRangeRef = getRepeatingColumns();
  4053. setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef);
  4054. }
  4055. @Override
  4056. public void setRepeatingColumns(CellRangeAddress columnRangeRef) {
  4057. CellRangeAddress rowRangeRef = getRepeatingRows();
  4058. setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef);
  4059. }
  4060. private void setRepeatingRowsAndColumns(
  4061. CellRangeAddress rowDef, CellRangeAddress colDef) {
  4062. int col1 = -1;
  4063. int col2 = -1;
  4064. int row1 = -1;
  4065. int row2 = -1;
  4066. if (rowDef != null) {
  4067. row1 = rowDef.getFirstRow();
  4068. row2 = rowDef.getLastRow();
  4069. if ((row1 == -1 && row2 != -1)
  4070. || row1 < -1 || row2 < -1 || row1 > row2) {
  4071. throw new IllegalArgumentException("Invalid row range specification");
  4072. }
  4073. }
  4074. if (colDef != null) {
  4075. col1 = colDef.getFirstColumn();
  4076. col2 = colDef.getLastColumn();
  4077. if ((col1 == -1 && col2 != -1)
  4078. || col1 < -1 || col2 < -1 || col1 > col2) {
  4079. throw new IllegalArgumentException(
  4080. "Invalid column range specification");
  4081. }
  4082. }
  4083. int sheetIndex = getWorkbook().getSheetIndex(this);
  4084. boolean removeAll = rowDef == null && colDef == null;
  4085. XSSFName name = getWorkbook().getBuiltInName(
  4086. XSSFName.BUILTIN_PRINT_TITLE, sheetIndex);
  4087. if (removeAll) {
  4088. if (name != null) {
  4089. getWorkbook().removeName(name);
  4090. }
  4091. return;
  4092. }
  4093. if (name == null) {
  4094. name = getWorkbook().createBuiltInName(
  4095. XSSFName.BUILTIN_PRINT_TITLE, sheetIndex);
  4096. }
  4097. String reference = getReferenceBuiltInRecord(
  4098. name.getSheetName(), col1, col2, row1, row2);
  4099. name.setRefersToFormula(reference);
  4100. // If the print setup isn't currently defined, then add it
  4101. // in but without printer defaults
  4102. // If it's already there, leave it as-is!
  4103. if (worksheet.isSetPageSetup() && worksheet.isSetPageMargins()) {
  4104. // Everything we need is already there
  4105. } else {
  4106. // Have initial ones put in place
  4107. getPrintSetup().setValidSettings(false);
  4108. }
  4109. }
  4110. private static String getReferenceBuiltInRecord(
  4111. String sheetName, int startC, int endC, int startR, int endR) {
  4112. // Excel example for built-in title:
  4113. // 'second sheet'!$E:$F,'second sheet'!$2:$3
  4114. CellReference colRef =
  4115. new CellReference(sheetName, 0, startC, true, true);
  4116. CellReference colRef2 =
  4117. new CellReference(sheetName, 0, endC, true, true);
  4118. CellReference rowRef =
  4119. new CellReference(sheetName, startR, 0, true, true);
  4120. CellReference rowRef2 =
  4121. new CellReference(sheetName, endR, 0, true, true);
  4122. String escapedName = SheetNameFormatter.format(sheetName);
  4123. String c = "";
  4124. String r = "";
  4125. if (startC != -1 || endC != -1) {
  4126. String col1 = colRef.getCellRefParts()[2];
  4127. String col2 = colRef2.getCellRefParts()[2];
  4128. c = escapedName + "!$" + col1 + ":$" + col2;
  4129. }
  4130. if (startR != -1 || endR != -1) {
  4131. String row1 = rowRef.getCellRefParts()[1];
  4132. String row2 = rowRef2.getCellRefParts()[1];
  4133. if (!row1.equals("0") && !row2.equals("0")) {
  4134. r = escapedName + "!$" + row1 + ":$" + row2;
  4135. }
  4136. }
  4137. StringBuilder rng = new StringBuilder();
  4138. rng.append(c);
  4139. if(rng.length() > 0 && r.length() > 0) {
  4140. rng.append(',');
  4141. }
  4142. rng.append(r);
  4143. return rng.toString();
  4144. }
  4145. private CellRangeAddress getRepeatingRowsOrColumns(boolean rows) {
  4146. int sheetIndex = getWorkbook().getSheetIndex(this);
  4147. XSSFName name = getWorkbook().getBuiltInName(
  4148. XSSFName.BUILTIN_PRINT_TITLE, sheetIndex);
  4149. if (name == null ) {
  4150. return null;
  4151. }
  4152. String refStr = name.getRefersToFormula();
  4153. if (refStr == null) {
  4154. return null;
  4155. }
  4156. String[] parts = refStr.split(",");
  4157. int maxRowIndex = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
  4158. int maxColIndex = SpreadsheetVersion.EXCEL2007.getLastColumnIndex();
  4159. for (String part : parts) {
  4160. CellRangeAddress range = CellRangeAddress.valueOf(part);
  4161. if ((range.getFirstColumn() == 0
  4162. && range.getLastColumn() == maxColIndex)
  4163. || (range.getFirstColumn() == -1
  4164. && range.getLastColumn() == -1)) {
  4165. if (rows) {
  4166. return range;
  4167. }
  4168. } else if (range.getFirstRow() == 0
  4169. && range.getLastRow() == maxRowIndex
  4170. || (range.getFirstRow() == -1
  4171. && range.getLastRow() == -1)) {
  4172. if (!rows) {
  4173. return range;
  4174. }
  4175. }
  4176. }
  4177. return null;
  4178. }
  4179. /**
  4180. * Creates an empty XSSFPivotTable and sets up all its relationships
  4181. * including: pivotCacheDefinition, pivotCacheRecords
  4182. * @return returns a pivotTable
  4183. */
  4184. @Beta
  4185. private XSSFPivotTable createPivotTable() {
  4186. XSSFWorkbook wb = getWorkbook();
  4187. List<XSSFPivotTable> pivotTables = wb.getPivotTables();
  4188. int tableId = getWorkbook().getPivotTables().size()+1;
  4189. //Create relationship between pivotTable and the worksheet
  4190. XSSFPivotTable pivotTable = (XSSFPivotTable) createRelationship(XSSFRelation.PIVOT_TABLE,
  4191. getWorkbook().getXssfFactory(), tableId);
  4192. pivotTable.setParentSheet(this);
  4193. pivotTables.add(pivotTable);
  4194. XSSFWorkbook workbook = getWorkbook();
  4195. //Create relationship between the pivot cache definition and the workbook
  4196. XSSFPivotCacheDefinition pivotCacheDefinition = (XSSFPivotCacheDefinition) workbook.
  4197. createRelationship(XSSFRelation.PIVOT_CACHE_DEFINITION, getWorkbook().getXssfFactory(), tableId);
  4198. String rId = workbook.getRelationId(pivotCacheDefinition);
  4199. //Create relationship between pivotTable and pivotCacheDefinition without creating a new instance
  4200. PackagePart pivotPackagePart = pivotTable.getPackagePart();
  4201. pivotPackagePart.addRelationship(pivotCacheDefinition.getPackagePart().getPartName(),
  4202. TargetMode.INTERNAL, XSSFRelation.PIVOT_CACHE_DEFINITION.getRelation());
  4203. pivotTable.setPivotCacheDefinition(pivotCacheDefinition);
  4204. //Create pivotCache and sets up its relationship with the workbook
  4205. pivotTable.setPivotCache(new XSSFPivotCache(workbook.addPivotCache(rId)));
  4206. //Create relationship between pivotcacherecord and pivotcachedefinition
  4207. XSSFPivotCacheRecords pivotCacheRecords = (XSSFPivotCacheRecords) pivotCacheDefinition.
  4208. createRelationship(XSSFRelation.PIVOT_CACHE_RECORDS, getWorkbook().getXssfFactory(), tableId);
  4209. //Set relationships id for pivotCacheDefinition to pivotCacheRecords
  4210. pivotTable.getPivotCacheDefinition().getCTPivotCacheDefinition().setId(pivotCacheDefinition.getRelationId(pivotCacheRecords));
  4211. wb.setPivotTables(pivotTables);
  4212. return pivotTable;
  4213. }
  4214. /**
  4215. * Create a pivot table using the AreaReference range on sourceSheet, at the given position.
  4216. * If the source reference contains a sheet name, it must match the sourceSheet
  4217. * @param source location of pivot data
  4218. * @param position A reference to the top left cell where the pivot table will start
  4219. * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name
  4220. * @throws IllegalArgumentException if source references a sheet different than sourceSheet
  4221. * @return The pivot table
  4222. */
  4223. @Beta
  4224. public XSSFPivotTable createPivotTable(final AreaReference source, CellReference position, Sheet sourceSheet) {
  4225. final String sourceSheetName = source.getFirstCell().getSheetName();
  4226. if(sourceSheetName != null && !sourceSheetName.equalsIgnoreCase(sourceSheet.getSheetName())) {
  4227. throw new IllegalArgumentException("The area is referenced in another sheet than the "
  4228. + "defined source sheet " + sourceSheet.getSheetName() + ".");
  4229. }
  4230. return createPivotTable(position, sourceSheet, wsSource -> {
  4231. final String[] firstCell = source.getFirstCell().getCellRefParts();
  4232. final String firstRow = firstCell[1];
  4233. final String firstCol = firstCell[2];
  4234. final String[] lastCell = source.getLastCell().getCellRefParts();
  4235. final String lastRow = lastCell[1];
  4236. final String lastCol = lastCell[2];
  4237. final String ref = firstCol+firstRow+':'+lastCol+lastRow; //or just source.formatAsString()
  4238. wsSource.setRef(ref);
  4239. });
  4240. }
  4241. /**
  4242. * Create a pivot table using the AreaReference or named/table range on sourceSheet, at the given position.
  4243. * If the source reference contains a sheet name, it must match the sourceSheet.
  4244. * @param position A reference to the top left cell where the pivot table will start
  4245. * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name
  4246. * @param refConfig A reference to the pivot table configurator
  4247. * @throws IllegalArgumentException if source references a sheet different than sourceSheet
  4248. * @return The pivot table
  4249. */
  4250. private XSSFPivotTable createPivotTable(CellReference position, Sheet sourceSheet, PivotTableReferenceConfigurator refConfig) {
  4251. XSSFPivotTable pivotTable = createPivotTable();
  4252. //Creates default settings for the pivot table
  4253. pivotTable.setDefaultPivotTableDefinition();
  4254. //Set sources and references
  4255. pivotTable.createSourceReferences(position, sourceSheet, refConfig);
  4256. //Create cachefield/s and empty SharedItems - must be after creating references
  4257. pivotTable.getPivotCacheDefinition().createCacheFields(sourceSheet);
  4258. pivotTable.createDefaultDataColumns();
  4259. return pivotTable;
  4260. }
  4261. /**
  4262. * Create a pivot table using the AreaReference range, at the given position.
  4263. * If the source reference contains a sheet name, that sheet is used, otherwise this sheet is assumed as the source sheet.
  4264. * @param source location of pivot data
  4265. * @param position A reference to the top left cell where the pivot table will start
  4266. * @return The pivot table
  4267. */
  4268. @Beta
  4269. public XSSFPivotTable createPivotTable(AreaReference source, CellReference position){
  4270. final String sourceSheetName = source.getFirstCell().getSheetName();
  4271. if(sourceSheetName != null && !sourceSheetName.equalsIgnoreCase(this.getSheetName())) {
  4272. final XSSFSheet sourceSheet = getWorkbook().getSheet(sourceSheetName);
  4273. return createPivotTable(source, position, sourceSheet);
  4274. }
  4275. return createPivotTable(source, position, this);
  4276. }
  4277. /**
  4278. * Create a pivot table using the Name range reference on sourceSheet, at the given position.
  4279. * If the source reference contains a sheet name, it must match the sourceSheet
  4280. * @param source location of pivot data
  4281. * @param position A reference to the top left cell where the pivot table will start
  4282. * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name
  4283. * @throws IllegalArgumentException if source references a sheet different than sourceSheet
  4284. * @return The pivot table
  4285. */
  4286. @Beta
  4287. public XSSFPivotTable createPivotTable(final Name source, CellReference position, Sheet sourceSheet) {
  4288. if(source.getSheetName() != null && !source.getSheetName().equals(sourceSheet.getSheetName())) {
  4289. throw new IllegalArgumentException("The named range references another sheet than the "
  4290. + "defined source sheet " + sourceSheet.getSheetName() + ".");
  4291. }
  4292. return createPivotTable(position, sourceSheet, wsSource -> wsSource.setName(source.getNameName()));
  4293. }
  4294. /**
  4295. * Create a pivot table using the Name range, at the given position.
  4296. * If the source reference contains a sheet name, that sheet is used, otherwise this sheet is assumed as the source sheet.
  4297. * @param source location of pivot data
  4298. * @param position A reference to the top left cell where the pivot table will start
  4299. * @return The pivot table
  4300. */
  4301. @Beta
  4302. public XSSFPivotTable createPivotTable(Name source, CellReference position) {
  4303. return createPivotTable(source, position, getWorkbook().getSheet(source.getSheetName()));
  4304. }
  4305. /**
  4306. * Create a pivot table using the Table, at the given position.
  4307. * Tables are required to have a sheet reference, so no additional logic around reference sheet is needed.
  4308. * @param source location of pivot data
  4309. * @param position A reference to the top left cell where the pivot table will start
  4310. * @return The pivot table
  4311. */
  4312. @Beta
  4313. public XSSFPivotTable createPivotTable(final Table source, CellReference position) {
  4314. return createPivotTable(position, getWorkbook().getSheet(source.getSheetName()), wsSource -> wsSource.setName(source.getName()));
  4315. }
  4316. /**
  4317. * Returns all the pivot tables for this Sheet
  4318. */
  4319. @Beta
  4320. public List<XSSFPivotTable> getPivotTables() {
  4321. List<XSSFPivotTable> tables = new ArrayList<>();
  4322. for (XSSFPivotTable table : getWorkbook().getPivotTables()) {
  4323. if (table.getParent() == this) {
  4324. tables.add(table);
  4325. }
  4326. }
  4327. return tables;
  4328. }
  4329. @Override
  4330. public int getColumnOutlineLevel(int columnIndex) {
  4331. CTCol col = columnHelper.getColumn(columnIndex, false);
  4332. if (col == null) {
  4333. return 0;
  4334. }
  4335. return col.getOutlineLevel();
  4336. }
  4337. /**
  4338. * Add ignored errors (usually to suppress them in the UI of a consuming
  4339. * application).
  4340. *
  4341. * @param cell Cell.
  4342. * @param ignoredErrorTypes Types of error to ignore there.
  4343. */
  4344. public void addIgnoredErrors(CellReference cell, IgnoredErrorType... ignoredErrorTypes) {
  4345. addIgnoredErrors(cell.formatAsString(false), ignoredErrorTypes);
  4346. }
  4347. /**
  4348. * Ignore errors across a range of cells.
  4349. *
  4350. * @param region Range of cells.
  4351. * @param ignoredErrorTypes Types of error to ignore there.
  4352. */
  4353. public void addIgnoredErrors(CellRangeAddress region, IgnoredErrorType... ignoredErrorTypes) {
  4354. region.validate(SpreadsheetVersion.EXCEL2007);
  4355. addIgnoredErrors(region.formatAsString(), ignoredErrorTypes);
  4356. }
  4357. /**
  4358. * Returns the errors currently being ignored and the ranges
  4359. * where they are ignored.
  4360. *
  4361. * @return Map of error type to the range(s) where they are ignored.
  4362. */
  4363. public Map<IgnoredErrorType, Set<CellRangeAddress>> getIgnoredErrors() {
  4364. Map<IgnoredErrorType, Set<CellRangeAddress>> result = new LinkedHashMap<>();
  4365. if (worksheet.isSetIgnoredErrors()) {
  4366. for (CTIgnoredError err : worksheet.getIgnoredErrors().getIgnoredErrorList()) {
  4367. for (IgnoredErrorType errType : XSSFIgnoredErrorHelper.getErrorTypes(err)) {
  4368. if (!result.containsKey(errType)) {
  4369. result.put(errType, new LinkedHashSet<>());
  4370. }
  4371. for (Object ref : err.getSqref()) {
  4372. result.get(errType).add(CellRangeAddress.valueOf(ref.toString()));
  4373. }
  4374. }
  4375. }
  4376. }
  4377. return result;
  4378. }
  4379. private void addIgnoredErrors(String ref, IgnoredErrorType... ignoredErrorTypes) {
  4380. CTIgnoredErrors ctIgnoredErrors = worksheet.isSetIgnoredErrors() ? worksheet.getIgnoredErrors() : worksheet.addNewIgnoredErrors();
  4381. CTIgnoredError ctIgnoredError = ctIgnoredErrors.addNewIgnoredError();
  4382. XSSFIgnoredErrorHelper.addIgnoredErrors(ctIgnoredError, ref, ignoredErrorTypes);
  4383. }
  4384. /**
  4385. * called when a sheet is being deleted/removed from a workbook, to clean up relations and other document pieces tied to the sheet
  4386. */
  4387. protected void onSheetDelete() {
  4388. for (RelationPart part : getRelationParts()) {
  4389. if (part.getDocumentPart() instanceof XSSFTable) {
  4390. // call table delete
  4391. removeTable(part.getDocumentPart());
  4392. continue;
  4393. }
  4394. removeRelation(part.getDocumentPart(), true);
  4395. }
  4396. }
  4397. /**
  4398. * when a cell with a 'master' shared formula is removed, the next cell in the range becomes the master
  4399. * @param cell The cell that is removed
  4400. * @param evalWb BaseXSSFEvaluationWorkbook in use, if one exists
  4401. */
  4402. protected void onDeleteFormula(final XSSFCell cell, final BaseXSSFEvaluationWorkbook evalWb) {
  4403. final int rowIndex = cell.getRowIndex();
  4404. final int columnIndex = cell.getColumnIndex();
  4405. CTCellFormula f = cell.getCTCell().getF();
  4406. if (f != null && f.getT() == STCellFormulaType.SHARED && f.isSetRef() && f.getStringValue() != null) {
  4407. CellRangeAddress ref = CellRangeAddress.valueOf(f.getRef());
  4408. if(ref.getNumberOfCells() > 1){
  4409. DONE:
  4410. for(int i = rowIndex; i <= ref.getLastRow(); i++){
  4411. XSSFRow row = getRow(i);
  4412. if(row != null) {
  4413. for(int j = columnIndex; j <= ref.getLastColumn(); j++){
  4414. XSSFCell nextCell = row.getCell(j);
  4415. if(nextCell != null && nextCell != cell && nextCell.getCellType() == CellType.FORMULA) {
  4416. CTCellFormula nextF = nextCell.getCTCell().getF();
  4417. if (nextF.getT() == STCellFormulaType.SHARED && nextF.getSi() == f.getSi()) {
  4418. nextF.setStringValue(nextCell.getCellFormula(evalWb));
  4419. CellRangeAddress nextRef = new CellRangeAddress(
  4420. nextCell.getRowIndex(), ref.getLastRow(),
  4421. nextCell.getColumnIndex(), ref.getLastColumn());
  4422. nextF.setRef(nextRef.formatAsString());
  4423. sharedFormulas.put(Math.toIntExact(nextF.getSi()), nextF);
  4424. break DONE;
  4425. }
  4426. }
  4427. }
  4428. }
  4429. }
  4430. }
  4431. }
  4432. }
  4433. /**
  4434. * Determine the OleObject which links shapes with embedded resources
  4435. *
  4436. * @param shapeId the shape id
  4437. * @return the CTOleObject of the shape
  4438. */
  4439. protected CTOleObject readOleObject(long shapeId) {
  4440. if (!getCTWorksheet().isSetOleObjects()) {
  4441. return null;
  4442. }
  4443. // we use a XmlCursor here to handle oleObject with-/out AlternateContent wrappers
  4444. String xquery = "declare namespace p='"+XSSFRelation.NS_SPREADSHEETML+"' .//p:oleObject";
  4445. try (XmlCursor cur = getCTWorksheet().getOleObjects().newCursor()) {
  4446. cur.selectPath(xquery);
  4447. CTOleObject coo = null;
  4448. while (cur.toNextSelection()) {
  4449. String sId = cur.getAttributeText(new QName(null, "shapeId"));
  4450. if (sId == null || Long.parseLong(sId) != shapeId) {
  4451. continue;
  4452. }
  4453. XmlObject xObj = cur.getObject();
  4454. if (xObj instanceof CTOleObject) {
  4455. // the unusual case ...
  4456. coo = (CTOleObject)xObj;
  4457. } else {
  4458. XMLStreamReader reader = cur.newXMLStreamReader();
  4459. try {
  4460. CTOleObjects coos = CTOleObjects.Factory.parse(reader);
  4461. if (coos.sizeOfOleObjectArray() == 0) {
  4462. continue;
  4463. }
  4464. coo = coos.getOleObjectArray(0);
  4465. } catch (XmlException e) {
  4466. LOG.atInfo().withThrowable(e).log("can't parse CTOleObjects");
  4467. } finally {
  4468. try {
  4469. reader.close();
  4470. } catch (XMLStreamException e) {
  4471. LOG.atInfo().withThrowable(e).log("can't close reader");
  4472. }
  4473. }
  4474. }
  4475. // there are choice and fallback OleObject ... we prefer the one having the objectPr element,
  4476. // which is in the choice element
  4477. if (cur.toChild(XSSFRelation.NS_SPREADSHEETML, "objectPr")) {
  4478. break;
  4479. }
  4480. }
  4481. return coo;
  4482. }
  4483. }
  4484. public XSSFHeaderFooterProperties getHeaderFooterProperties() {
  4485. return new XSSFHeaderFooterProperties(getSheetTypeHeaderFooter());
  4486. }
  4487. /**
  4488. * Currently, this is for internal use. Overrides the default dimensions of the sheet.
  4489. * @param dimension {@link CellRangeAddress}, <code>null</code> removes the existing override
  4490. * @since POI 5.2.3
  4491. */
  4492. @Beta
  4493. public void setDimensionOverride(CellRangeAddress dimension) {
  4494. this.dimensionOverride = dimension;
  4495. }
  4496. static void cloneTables(XSSFSheet sheet) {
  4497. for (XSSFTable table : sheet.getTables()) {
  4498. // clone table; XSSFTable.setArea fails and throws exception for too small tables
  4499. XSSFTable clonedTable = null;
  4500. if (table.supportsAreaReference(table.getArea())) {
  4501. clonedTable = sheet.createTable(table.getArea());
  4502. }
  4503. if (clonedTable != null) {
  4504. clonedTable.updateHeaders();
  4505. // clone style
  4506. clonedTable.setStyleName(table.getStyleName());
  4507. XSSFTableStyleInfo style = (XSSFTableStyleInfo)table.getStyle();
  4508. XSSFTableStyleInfo clonedStyle = (XSSFTableStyleInfo)clonedTable.getStyle();
  4509. if (style != null && clonedStyle != null) {
  4510. clonedStyle.setShowColumnStripes(style.isShowColumnStripes());
  4511. clonedStyle.setShowRowStripes(style.isShowRowStripes());
  4512. clonedStyle.setFirstColumn(style.isShowFirstColumn());
  4513. clonedStyle.setLastColumn(style.isShowLastColumn());
  4514. }
  4515. //clone autofilter
  4516. clonedTable.getCTTable().setAutoFilter(table.getCTTable().getAutoFilter());
  4517. //clone totalsrow
  4518. int totalsRowCount = table.getTotalsRowCount();
  4519. if (totalsRowCount == 1) { // never seen more than one totals row
  4520. XSSFRow totalsRow = sheet.getRow(clonedTable.getEndCellReference().getRow());
  4521. if (clonedTable.getCTTable().getTableColumns() != null
  4522. && !clonedTable.getCTTable().getTableColumns().getTableColumnList().isEmpty()) {
  4523. clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
  4524. for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
  4525. CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
  4526. CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
  4527. clonedTableCol.setTotalsRowFunction(tableCol.getTotalsRowFunction());
  4528. int intTotalsRowFunction = clonedTableCol.getTotalsRowFunction().intValue();
  4529. sheet.getWorkbook().setCellFormulaValidation(false);
  4530. if (intTotalsRowFunction == 10) { //custom
  4531. CTTableFormula totalsRowFormula = tableCol.getTotalsRowFormula();
  4532. clonedTableCol.setTotalsRowFormula(totalsRowFormula);
  4533. totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(totalsRowFormula.getStringValue());
  4534. } else if (intTotalsRowFunction == 1) { //none
  4535. //totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setBlank();
  4536. } else {
  4537. String subtotalFormulaStart = getSubtotalFormulaStartFromTotalsRowFunction(intTotalsRowFunction);
  4538. if (subtotalFormulaStart != null)
  4539. totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(subtotalFormulaStart + "," + clonedTable.getName() +"[" + clonedTableCol.getName()+ "])");
  4540. }
  4541. }
  4542. }
  4543. }
  4544. // clone calculated column formulas
  4545. if (clonedTable.getCTTable().getTableColumns() != null
  4546. && !clonedTable.getCTTable().getTableColumns().getTableColumnList().isEmpty()) {
  4547. clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
  4548. for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
  4549. CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
  4550. CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
  4551. if (tableCol.getCalculatedColumnFormula() != null) {
  4552. clonedTableCol.setCalculatedColumnFormula(tableCol.getCalculatedColumnFormula());
  4553. CTTableFormula calculatedColumnFormula = clonedTableCol.getCalculatedColumnFormula();
  4554. String formula = tableCol.getCalculatedColumnFormula().getStringValue();
  4555. String clonedFormula = formula.replace(table.getName(), clonedTable.getName());
  4556. calculatedColumnFormula.setStringValue(clonedFormula);
  4557. int rFirst = clonedTable.getStartCellReference().getRow() + clonedTable.getHeaderRowCount();
  4558. int rLast = clonedTable.getEndCellReference().getRow() - clonedTable.getTotalsRowCount();
  4559. int c = clonedTable.getStartCellReference().getCol() + i;
  4560. sheet.getWorkbook().setCellFormulaValidation(false);
  4561. for (int r = rFirst; r <= rLast; r++) {
  4562. XSSFRow row = sheet.getRow(r);
  4563. if (row == null) row = sheet.createRow(r);
  4564. XSSFCell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
  4565. cell.setCellFormula(clonedFormula);
  4566. }
  4567. }
  4568. }
  4569. }
  4570. }
  4571. // remove old table
  4572. sheet.removeTable(table);
  4573. }
  4574. }
  4575. private static String getSubtotalFormulaStartFromTotalsRowFunction(int intTotalsRowFunction) {
  4576. final int INT_NONE = 1;
  4577. final int INT_SUM = 2;
  4578. final int INT_MIN = 3;
  4579. final int INT_MAX = 4;
  4580. final int INT_AVERAGE = 5;
  4581. final int INT_COUNT = 6;
  4582. final int INT_COUNT_NUMS = 7;
  4583. final int INT_STD_DEV = 8;
  4584. final int INT_VAR = 9;
  4585. final int INT_CUSTOM = 10;
  4586. String subtotalFormulaStart = null;
  4587. switch (intTotalsRowFunction) {
  4588. case INT_NONE:
  4589. subtotalFormulaStart = null;
  4590. break;
  4591. case INT_SUM:
  4592. subtotalFormulaStart = "SUBTOTAL(109";
  4593. break;
  4594. case INT_MIN:
  4595. subtotalFormulaStart = "SUBTOTAL(105";
  4596. break;
  4597. case INT_MAX:
  4598. subtotalFormulaStart = "SUBTOTAL(104";
  4599. break;
  4600. case INT_AVERAGE:
  4601. subtotalFormulaStart = "SUBTOTAL(101";
  4602. break;
  4603. case INT_COUNT:
  4604. subtotalFormulaStart = "SUBTOTAL(103";
  4605. break;
  4606. case INT_COUNT_NUMS:
  4607. subtotalFormulaStart = "SUBTOTAL(102";
  4608. break;
  4609. case INT_STD_DEV:
  4610. subtotalFormulaStart = "SUBTOTAL(107";
  4611. break;
  4612. case INT_VAR:
  4613. subtotalFormulaStart = "SUBTOTAL(110";
  4614. break;
  4615. case INT_CUSTOM:
  4616. subtotalFormulaStart = null;
  4617. break;
  4618. default:
  4619. subtotalFormulaStart = null;
  4620. }
  4621. return subtotalFormulaStart;
  4622. }
  4623. }