001package com.hfg.xml.msofficexml.xlsx.spreadsheetml;
002
003
004import java.io.Writer;
005import java.util.*;
006import java.util.regex.Pattern;
007
008import com.hfg.exception.InvalidValueException;
009import com.hfg.exception.ProgrammingException;
010import com.hfg.util.StringBuilderPlus;
011import com.hfg.util.collection.CollectionUtil;
012import com.hfg.util.StringUtil;
013import com.hfg.xml.XMLNamespace;
014import com.hfg.xml.XMLNode;
015import com.hfg.xml.XMLTag;
016import com.hfg.xml.msofficexml.OfficeOpenXmlException;
017import com.hfg.xml.msofficexml.OfficeXML;
018import com.hfg.xml.msofficexml.docx.RelationshipXML;
019import com.hfg.xml.msofficexml.xlsx.CellRef;
020import com.hfg.xml.msofficexml.xlsx.spreadsheetDrawing.WorksheetDrawing;
021import com.hfg.xml.msofficexml.xlsx.CellRange;
022import com.hfg.xml.msofficexml.xlsx.Xlsx;
023import com.hfg.xml.msofficexml.xlsx.part.SsmlCommentsPart;
024import com.hfg.xml.msofficexml.xlsx.part.SsmlDrawingPart;
025import com.hfg.xml.msofficexml.xlsx.part.TablePart;
026import com.hfg.xml.msofficexml.xlsx.part.WorksheetPart;
027
028//------------------------------------------------------------------------------
029/**
030 Represents an Office Open XML sheet (<ssml:worksheet>) tag.
031 <div>
032  @author J. Alex Taylor, hairyfatguy.com
033 </div>
034 */
035//------------------------------------------------------------------------------
036// com.hfg XML/HTML Coding Library
037//
038// This library is free software; you can redistribute it and/or
039// modify it under the terms of the GNU Lesser General Public
040// License as published by the Free Software Foundation; either
041// version 2.1 of the License, or (at your option) any later version.
042//
043// This library is distributed in the hope that it will be useful,
044// but WITHOUT ANY WARRANTY; without even the implied warranty of
045// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
046// Lesser General Public License for more details.
047//
048// You should have received a copy of the GNU Lesser General Public
049// License along with this library; if not, write to the Free Software
050// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
051//
052// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
053// jataylor@hairyfatguy.com
054//------------------------------------------------------------------------------
055
056public class SsmlWorksheet extends SsmlXMLTag
057{
058   //###########################################################################
059   // PRIVATE FIELDS
060   //###########################################################################
061
062   private WorksheetPart    mParentWorksheetPart;
063   private SsmlCommentsPart mCommentsPart;
064   private String           mName;
065   private SsmlSheetData    mSheetData;
066   private SsmlPageMargins  mPageMargins;
067   private SsmlSheetFormatProperties mSheetFormatPropertiesTag;
068   private XMLTag           mSheetViewsTag;
069   private XMLTag           mTablePartsTag;
070   private XMLTag           mMergeCellsTag;
071   private XMLTag           mColumnsTag;
072   private XMLTag           mDimensionTag;
073   private XMLTag           mDataValidationsTag;
074   private SsmlHeaderFooter mHeaderFooterTag;
075   private XMLTag           mExtLst;
076   private SsmlSortState    mSortState;
077
078   private Map<String, TablePart> mTableParts;
079   private Map<String, SsmlDrawingPart> mDrawingParts;
080
081   // Each sheet needs a unique GUID
082   private final String mUID = UUID.randomUUID().toString().toUpperCase();
083
084   // There are seven bad characters for sheet names: \ / * [ ] : ?
085   private static final Pattern BAD_SHEET_NAME_CHAR_PATTERN = Pattern.compile("[\\\\\\/\\*\\[\\]\\:\\?+]");
086
087   private static final int MAX_SHEET_NAME_LENGTH = 31;
088
089   private static final String PRINT_AREA_NAME = "_xlnm.Print_Area";
090
091
092   //###########################################################################
093   // CONSTRUCTORS
094   //###########################################################################
095
096   //---------------------------------------------------------------------------
097   public SsmlWorksheet(WorksheetPart inParentWorksheetPart)
098   {
099      super(SsmlXML.WORKSHEET, (Xlsx) inParentWorksheetPart.getParentDoc());
100
101      mParentWorksheetPart = inParentWorksheetPart;
102
103      addXMLNamespaceDeclaration(RelationshipXML.RELATIONSHIP_NAMESPACE);
104      addXMLNamespaceDeclaration(OfficeXML.MARKUP_CAPABILITY_NAMESPACE);
105      addXMLNamespaceDeclaration(OfficeXML.MAC_VML_NAMESPACE);
106      addXMLNamespaceDeclaration(SsmlXML.SPREADSHEETML_2009AC_NAMESPACE);
107      addXMLNamespaceDeclaration(SsmlXML.SPREADSHEETML_REVISION1_NAMESPACE);
108      addXMLNamespaceDeclaration(SsmlXML.SPREADSHEETML_REVISION2_NAMESPACE);
109      addXMLNamespaceDeclaration(SsmlXML.SPREADSHEETML_REVISION3_NAMESPACE);
110
111      StringBuilderPlus ignorableBuffer = new StringBuilderPlus().setDelimiter(" ")
112            .delimitedAppend(SsmlXML.SPREADSHEETML_2009AC_NAMESPACE.getPrefix())
113            .delimitedAppend(SsmlXML.SPREADSHEETML_REVISION1_NAMESPACE.getPrefix())
114            .delimitedAppend(SsmlXML.SPREADSHEETML_REVISION2_NAMESPACE.getPrefix())
115            .delimitedAppend(SsmlXML.SPREADSHEETML_REVISION3_NAMESPACE.getPrefix());
116
117      setAttribute(OfficeXML.IGNORABLE_ATT, ignorableBuffer.toString());
118
119      // Specify the sheet's GUID
120      setAttribute(SsmlXML.XR_UID_ATT, "{" + mUID + "}");
121
122      getPageMargins();
123   }
124
125   //---------------------------------------------------------------------------
126   public SsmlWorksheet(WorksheetPart inParentWorksheetPart, String inName, XMLTag inSheetTag)
127   {
128      this(inParentWorksheetPart);
129
130      String name = inSheetTag.getAttributeValue(SsmlXML.NAME_ATT);
131      setName(StringUtil.isSet(name) ? name : inName);
132
133      try
134      {
135         XMLTag sheetDataTag = inSheetTag.getRequiredSubtagByName(SsmlXML.SHEET_DATA);
136         mSheetData = new SsmlSheetData(this, sheetDataTag);
137
138         // TODO: Parse other pieces
139      }
140      catch (Exception e)
141      {
142         throw new OfficeOpenXmlException("Problem parsing worksheet " + StringUtil.singleQuote(getName()) + "!", e);
143      }
144   }
145
146   //###########################################################################
147   // PUBLIC METHODS
148   //###########################################################################
149
150   //---------------------------------------------------------------------------
151   public List<String> toTSV()
152   {
153      List<String> lines = new ArrayList<>();
154      
155      for (SsmlRow row : getSheetData().getRows())
156      {
157         // Fill in missing lines
158         while (lines.size() < row.getRowIndex() - 1)
159         {
160            lines.add("");   
161         }
162
163         StringBuilderPlus lineBuffer = new StringBuilderPlus().setDelimiter("\t");
164         List<SsmlCell> cells = row.getCells();
165         if (CollectionUtil.hasValues(cells))
166         {
167            SsmlCell prevCell = null;
168            int cellCount = 0;
169            for (SsmlCell cell : cells)
170            {
171               cellCount++;
172
173               int leadingEmptyCells = cell.getRef().getColIndex() - (prevCell != null ? prevCell.getRef().getColIndex() : 0) - 1;
174               if (leadingEmptyCells > 0)
175               {
176                  lineBuffer.delimitedAppend(StringUtil.polyChar('\t', leadingEmptyCells));
177               }
178               
179               Object value = cell.getValue();
180               String stringValue = (value != null ? value.toString() : "");
181               // Handle multi-line cells
182               if (stringValue.indexOf('\n', 0) > 0)
183               {
184                  stringValue = StringUtil.quote(StringUtil.replaceAllRegexp(stringValue, "\r?\n", "\\n"));
185               }
186
187               // Was using delimitedAppend() but it won't separate blank cells
188               if (cellCount > 1)
189               {
190                  lineBuffer.append("\t");
191               }
192               lineBuffer.append(stringValue);
193
194               prevCell = cell;
195            }
196
197            lines.add(lineBuffer.toString());
198         }
199      }
200      
201      return lines;
202   }
203   //---------------------------------------------------------------------------
204   @Override
205   public String toString()
206   {
207      return getName();
208   }
209
210   //---------------------------------------------------------------------------
211   public WorksheetPart getParentWorksheetPart()
212   {
213      return mParentWorksheetPart;
214   }
215
216   //---------------------------------------------------------------------------
217   public SsmlCommentsPart getCommentsPart()
218   {
219      if (null == mCommentsPart)
220      {
221         mCommentsPart = new SsmlCommentsPart(this);
222      }
223
224      return mCommentsPart;
225   }
226
227   //---------------------------------------------------------------------------
228   public SsmlWorksheet setName(String inValue)
229   {
230      mName = null;
231
232      if (StringUtil.isSet(inValue))
233      {
234         mName = inValue;
235
236         // Remove illegal sheet name characters
237         mName = StringUtil.replaceAllRegexp(mName, BAD_SHEET_NAME_CHAR_PATTERN, "_");
238
239         // Truncate sheet names that are too long
240         if (mName.length() > MAX_SHEET_NAME_LENGTH)
241         {
242            mName = mName.substring(0, MAX_SHEET_NAME_LENGTH);
243         }
244      }
245
246      setAttribute(SsmlXML.NAME_ATT, mName);
247
248      return this;
249   }
250
251   //---------------------------------------------------------------------------
252   public String getName()
253   {
254      return mName;
255   }
256
257   //---------------------------------------------------------------------------
258   public SsmlSheetData getSheetData()
259   {
260      if (null == mSheetData)
261      {
262         // Check it it has been added via addSubtag()...
263         mSheetData = getOptionalSubtagByName(SsmlXML.SHEET_DATA);
264         if (null == mSheetData)
265         {
266            mSheetData = new SsmlSheetData(this);
267            addSubtag(mSheetData);
268         }
269      }
270
271      return mSheetData;
272   }
273
274   //---------------------------------------------------------------------------
275   public void mergeCells(CellRange inCellRange)
276   {
277      XMLTag mergeCellsTag = getMergeCellsTag();
278
279      XMLTag mergeCellTag = new XMLTag(SsmlXML.MERGE_CELL);
280      mergeCellTag.setAttribute(SsmlXML.REF_RANGE_ATT, inCellRange);
281
282      mergeCellsTag.addSubtag(mergeCellTag);
283
284      mergeCellsTag.setAttribute(SsmlXML.COUNT_ATT, mergeCellsTag.getSubtags().size());
285   }
286
287   //---------------------------------------------------------------------------
288   public void removeMergeCell(CellRange inCellRange)
289   {
290      XMLTag mergeCellsTag = getMergeCellsTag();
291      for (XMLNode mergeCellTag : mergeCellsTag.getSubtagsByName(SsmlXML.MERGE_CELL))
292      {
293         if (mergeCellTag.getAttributeValue(SsmlXML.REF_RANGE_ATT).equals(inCellRange.toString()))
294         {
295            mergeCellsTag.removeSubtag(mergeCellTag);
296            break;
297         }
298      }
299
300      mergeCellsTag.setAttribute(SsmlXML.COUNT_ATT, mergeCellsTag.getSubtags().size());
301   }
302
303   //---------------------------------------------------------------------------
304   public SsmlCol addColumn()
305   {
306      if (null == mColumnsTag)
307      {
308         // Check it it has been added via addSubtag()...
309         mColumnsTag = getOptionalSubtagByName(SsmlXML.COLUMNS);
310         if (null == mColumnsTag)
311         {
312            mColumnsTag = new XMLTag(SsmlXML.COLUMNS);
313            addSubtag(mColumnsTag);
314         }
315      }
316
317      SsmlCol col = new SsmlCol(this);
318      mColumnsTag.addSubtag(col);
319
320      return col;
321   }
322
323   //---------------------------------------------------------------------------
324   public SsmlTable addTable()
325   {
326      if (null == mTableParts)
327      {
328         mTableParts = new HashMap<>(10);
329
330         mTablePartsTag = new XMLTag(SsmlXML.TABLE_PARTS);
331         addSubtag(mTablePartsTag);
332      }
333
334      TablePart tablePart = new TablePart(getParentWorksheetPart()).setTableIndex(mTableParts.size() + 1);
335      addTablePart(tablePart);
336
337      return tablePart.getRootNode();
338   }
339
340   //---------------------------------------------------------------------------
341   public SsmlDataValidation addDataValidation(CellRange inCellRange)
342   {
343      if (null == mDataValidationsTag)
344      {
345         // Check it it has been added via addSubtag()...
346         mDataValidationsTag = getOptionalSubtagByName(SsmlXML.DATA_VALIDATIONS);
347         if (null == mDataValidationsTag)
348         {
349            mDataValidationsTag = new XMLTag(SsmlXML.DATA_VALIDATIONS);
350            addSubtag(mDataValidationsTag);
351         }
352      }
353
354
355      SsmlDataValidation dataValidation = new SsmlDataValidation(getParentDoc(), inCellRange);
356      mDataValidationsTag.addSubtag(dataValidation);
357
358
359      mDataValidationsTag.setAttribute(SsmlXML.COUNT_ATT, mDataValidationsTag.getSubtagsByName(SsmlXML.DATA_VALIDATION).size());
360
361
362      return dataValidation;
363   }
364
365   //---------------------------------------------------------------------------
366   /**
367    Returns all table definitions.
368    */
369   public Collection<SsmlTable> getTables()
370   {
371      ArrayList<SsmlTable> tables = new ArrayList<SsmlTable>(mTableParts.size());
372      if (CollectionUtil.hasValues(mTableParts))
373      {
374         for (TablePart part : mTableParts.values())
375         {
376            tables.add(part.getRootNode());
377         }
378      }
379
380      return tables;
381   }
382
383   //---------------------------------------------------------------------------
384   /**
385    Returns a table by name.
386    * @param inTableName the name of the table
387    * @return SsmlTable
388    */
389   public SsmlTable getTable(String inTableName)
390   {
391      TablePart part =  mTableParts.get(inTableName);
392      return (part != null ? part.getRootNode() : null);
393   }
394
395
396   //---------------------------------------------------------------------------
397   public SsmlWorksheet setDimension(CellRange inCellRange)
398   {
399      if (null == mDimensionTag)
400      {
401         // Check it it has been added via addSubtag()...
402         mDimensionTag = getOptionalSubtagByName(SsmlXML.DIMENSION);
403         if (null == mDimensionTag)
404         {
405            mDimensionTag = new XMLTag(SsmlXML.DIMENSION);
406            addSubtag(mDimensionTag);
407         }
408      }
409
410      mDimensionTag.setAttribute(SsmlXML.REF_RANGE_ATT, inCellRange);
411
412      return this;
413   }
414
415   //---------------------------------------------------------------------------
416   public SsmlSheetFormatProperties getSheetFormatProperties()
417   {
418      if (null == mSheetFormatPropertiesTag)
419      {
420         // Check it it has been added via addSubtag()...
421         mSheetFormatPropertiesTag = getOptionalSubtagByName(SsmlXML.SHEET_FORMAT_PROPS);
422         if (null == mSheetFormatPropertiesTag)
423         {
424            mSheetFormatPropertiesTag = new SsmlSheetFormatProperties(getParentDoc());
425            addSubtag(mSheetFormatPropertiesTag);
426         }
427      }
428
429      return mSheetFormatPropertiesTag;
430   }
431
432
433   //---------------------------------------------------------------------------
434   public SsmlPageMargins getPageMargins()
435   {
436      if (null == mPageMargins)
437      {
438         // Check it it has been added via addSubtag()...
439         mPageMargins = getOptionalSubtagByName(SsmlXML.PAGE_MARGINS);
440         if (null == mPageMargins)
441         {
442            mPageMargins = new SsmlPageMargins(getParentDoc());
443            addSubtag(mPageMargins);
444         }
445      }
446
447      return mPageMargins;
448   }
449
450   //---------------------------------------------------------------------------
451   public SsmlSheetView addSheetView()
452   {
453      if (null == mSheetViewsTag)
454      {
455         mSheetViewsTag = new XMLTag(SsmlXML.SHEET_VIEWS);
456         addSubtag(mSheetViewsTag);
457      }
458
459      SsmlSheetView sheetView = new SsmlSheetView(getParentDoc());
460      mSheetViewsTag.addSubtag(sheetView);
461
462      return sheetView;
463   }
464
465   //---------------------------------------------------------------------------
466   public List<SsmlSheetView> getSheetViews()
467   {
468      List<SsmlSheetView> views = null;
469
470      if (mSheetViewsTag != null)
471      {
472         views = mSheetViewsTag.getSubtagsByName(SsmlXML.SHEET_VIEW);
473      }
474
475      return views;
476   }
477
478
479   //---------------------------------------------------------------------------
480   public SsmlWorksheet setHeader(String inValue)
481   {
482      if (null == mHeaderFooterTag)
483      {
484         // Check it it has been added via addSubtag()...
485         mHeaderFooterTag = getOptionalSubtagByName(SsmlXML.HEADER_FOOTER);
486      }
487
488      if (StringUtil.isSet(inValue))
489      {
490         if (null == mHeaderFooterTag)
491         {
492            mHeaderFooterTag = new SsmlHeaderFooter(getParentDoc());
493            addSubtag(mHeaderFooterTag);
494         }
495
496         mHeaderFooterTag.setHeader(inValue);
497      }
498      else if (mHeaderFooterTag != null)
499      {
500         mHeaderFooterTag.setHeader(null);
501      }
502
503      return this;
504   }
505
506   //---------------------------------------------------------------------------
507   public String getHeader()
508   {
509      if (null == mHeaderFooterTag)
510      {
511         // Check it it has been added via addSubtag()...
512         mHeaderFooterTag = getOptionalSubtagByName(SsmlXML.HEADER_FOOTER);
513      }
514
515      return (mHeaderFooterTag != null ? mHeaderFooterTag.getHeader() : null);
516   }
517
518   //---------------------------------------------------------------------------
519   public SsmlWorksheet setFooter(String inValue)
520   {
521      if (null == mHeaderFooterTag)
522      {
523         // Check it it has been added via addSubtag()...
524         mHeaderFooterTag = getOptionalSubtagByName(SsmlXML.HEADER_FOOTER);
525      }
526
527      if (StringUtil.isSet(inValue))
528      {
529         if (null == mHeaderFooterTag)
530         {
531            mHeaderFooterTag = new SsmlHeaderFooter(getParentDoc());
532            addSubtag(mHeaderFooterTag);
533         }
534
535         mHeaderFooterTag.setFooter(inValue);
536      }
537      else if (mHeaderFooterTag != null)
538      {
539         mHeaderFooterTag.setFooter(null);
540      }
541
542      return this;
543   }
544
545   //---------------------------------------------------------------------------
546   public String getFooter()
547   {
548      if (null == mHeaderFooterTag)
549      {
550         // Check it it has been added via addSubtag()...
551         mHeaderFooterTag = getOptionalSubtagByName(SsmlXML.HEADER_FOOTER);
552      }
553
554      return (mHeaderFooterTag != null ? mHeaderFooterTag.getFooter() : null);
555   }
556
557
558
559   //---------------------------------------------------------------------------
560   public WorksheetDrawing addDrawing()
561   {
562      SsmlDrawingPart drawingPart = new SsmlDrawingPart(getParentWorksheetPart());
563      getParentDoc().addDrawingPart(drawingPart);// This will set the drawing part's index
564
565      addDrawingPart(drawingPart);
566
567      return drawingPart.getRootNode();
568   }
569
570   //---------------------------------------------------------------------------
571   public SsmlConditionalFormatting addConditionalFormatting(CellRange inCellRange)
572   {
573      SsmlConditionalFormatting conditionalFormatting = new SsmlConditionalFormatting(this, inCellRange);
574      addSubtag(conditionalFormatting);
575
576      return conditionalFormatting;
577   }
578
579   //---------------------------------------------------------------------------
580   public SsmlExtension getOrAddExtension(XMLNamespace inNamespace, SsmlExtension.URI_Type inURI_Type)
581   {
582      // Extensions are collected under an extLst tag
583      if (null == mExtLst)
584      {
585         // Check if it has been added via addSubtag()...
586         mExtLst = getOptionalSubtagByName(SsmlXML.EXTENSION_LIST);
587         if (null == mExtLst)
588         {
589            mExtLst = new XMLTag(SsmlXML.EXTENSION_LIST);
590            addSubtag(mExtLst);
591         }
592      }
593
594      SsmlExtension extension = mExtLst.getSubtagByAttribute(SsmlXML.URI_ATT, inURI_Type.getURI());
595      if (null == extension)
596      {
597         extension = new SsmlExtension(this, inNamespace, inURI_Type);
598         mExtLst.addSubtag(extension);
599      }
600
601      return extension;
602   }
603
604   //---------------------------------------------------------------------------
605   public void setPrintArea(CellRange inPrintArea)
606   {
607      getParentDoc().getWorkbookPart().getRootNode()
608            .setDefinedName(PRINT_AREA_NAME, this, StringUtil.singleQuote(getName())
609                                                   + "!$" + inPrintArea.getBeginCell().getCol() + "$" + inPrintArea.getBeginCell().getRowIndex()
610                                                   + ":$" + inPrintArea.getEndCell().getCol() + "$" + inPrintArea.getEndCell().getRowIndex());
611   }
612
613   //---------------------------------------------------------------------------
614   @Override
615   public void toXML(Writer inWriter)
616   {
617      finalizeSheet();
618      super.toXML(inWriter);
619   }
620
621   //---------------------------------------------------------------------------
622   @Override
623   public void toIndentedXML(Writer inWriter, int inInitialIndentLevel, int inIndentSize)
624   {
625      finalizeSheet();
626      super.toIndentedXML(inWriter, inInitialIndentLevel, inIndentSize);
627   }
628
629   //---------------------------------------------------------------------------
630   public SsmlWorksheet setSortState(SsmlSortState inValue)
631   {
632      mSortState = inValue;
633      if (inValue != null)
634      {
635         addSubtag(mSortState);
636      }
637      else
638      {
639         // Remove the subtag if it was already added
640         XMLTag subtag = getOptionalSubtagByName(SsmlXML.SORT_STATE);
641         if (subtag != null)
642         {
643            removeSubtag(subtag);
644         }
645      }
646
647      return this;
648   }
649
650
651   //###########################################################################
652   // PROTECTED METHODS
653   //###########################################################################
654
655   //---------------------------------------------------------------------------
656   /**
657    Called by the table when the user calls setName().
658    */
659   protected void renameTable(String inOldName, String inNewName)
660   {
661      if (! mTableParts.containsKey(inOldName))
662      {
663         throw new ProgrammingException("No table was found in this sheet with name " + StringUtil.singleQuote(inOldName) + "!");
664      }
665      else if (mTableParts.containsKey(inNewName))
666      {
667         throw new InvalidValueException("A table with name " + StringUtil.singleQuote(inNewName) + " already exists in this worksheet!");
668      }
669
670      TablePart tablePart = mTableParts.remove(inOldName);
671      mTableParts.put(inNewName, tablePart);
672   }
673
674   //###########################################################################
675   // PRIVATE METHODS
676   //###########################################################################
677
678   //---------------------------------------------------------------------------
679   private void addTablePart(TablePart inValue)
680   {
681      if (inValue != null)
682      {
683         SsmlTable table  = inValue.getRootNode();
684
685         // Ensure that the table has a unique name
686         if (! StringUtil.isSet(table.getName()))
687         {
688            table.setName("Table" + (mTableParts.size() + 1));
689         }
690
691         if (mTableParts.containsKey(table.getName()))
692         {
693            throw new OfficeOpenXmlException("The sheet name " + StringUtil.singleQuote(table.getName()) + " must be unique!");
694         }
695
696         mTableParts.put(table.getName(), inValue);
697
698         String relationshipId = getParentWorksheetPart().getWorksheetRelationshipPart().addTable(inValue);
699
700         XMLTag tablePartTag = new XMLTag(SsmlXML.TABLE_PART);
701         tablePartTag.setAttribute(RelationshipXML.ID_ATT, relationshipId);
702         mTablePartsTag.addSubtag(tablePartTag);
703
704         mTablePartsTag.setAttribute(SsmlXML.COUNT_ATT, mTableParts.size());
705      }
706   }
707
708   //---------------------------------------------------------------------------
709   private void addDrawingPart(SsmlDrawingPart inValue)
710   {
711      if (inValue != null)
712      {
713         String relationshipId = getParentWorksheetPart().getWorksheetRelationshipPart().addDrawing(inValue);
714
715         XMLTag drawingTag = new XMLTag(SsmlXML.DRAWING);
716         drawingTag.setAttribute(RelationshipXML.ID_ATT, relationshipId);
717         addSubtag(drawingTag);
718      }
719   }
720
721   //---------------------------------------------------------------------------
722   // Called before writing the sheet to XML.
723   private void finalizeSheet()
724   {
725      setDimension();
726
727      if (null == mSheetViewsTag)
728      {
729         addSheetView();
730      }
731   }
732
733   //---------------------------------------------------------------------------
734   // Adds (or adjusts) a dimension tag (ex: <dimension ref="A1:B2"/>).
735   private void setDimension()
736   {
737      // Determine the dimensions of the sheet
738      List<XMLTag> rowTags = getSheetData().getSubtagsByName(SsmlXML.ROW);
739      if (CollectionUtil.hasValues(rowTags))
740      {
741         Integer minRow = null;
742         Integer maxRow = null;
743         Integer minCol = null;
744         Integer maxCol = null;
745         for (XMLTag rowTag : rowTags)
746         {
747            List<XMLTag> cellTags = rowTag.getSubtagsByName(SsmlXML.CELL);
748            if (CollectionUtil.hasValues(cellTags))
749            {
750               CellRef firstCell = new CellRef(cellTags.get(0).getAttributeValue(SsmlXML.REF_ATT));
751               if (null == minRow)
752               {
753                  minRow = firstCell.getRowIndex();
754               }
755
756               maxRow = firstCell.getRowIndex();
757
758               if (null == minCol
759                     || firstCell.getColIndex() < minCol)
760               {
761                  minCol = firstCell.getColIndex();
762               }
763
764               CellRef lastCell = new CellRef(cellTags.get(cellTags.size() - 1).getAttributeValue(SsmlXML.REF_ATT));
765
766               if (null == maxCol
767                     || lastCell.getColIndex() > maxCol)
768               {
769                  maxCol = lastCell.getColIndex();
770               }
771            }
772         }
773
774         CellRef minCell = new CellRef().setRowIndex(minRow).setColIndex(minCol);
775         CellRef maxCell = new CellRef().setRowIndex(maxRow).setColIndex(maxCol);
776 /*
777         String minCell = "";
778         List<XMLTag> cellTags = rowTags.get(0).getSubtagsByName(SsmlXML.CELL);
779         if (CollectionUtil.hasValues(cellTags))
780         {
781            minCell = cellTags.get(0).getAttributeValue(SsmlXML.REF_ATT);
782         }
783
784         String maxCell = "";
785         cellTags = rowTags.get(rowTags.size() - 1).getSubtagsByName(SsmlXML.CELL);
786         if (CollectionUtil.hasValues(cellTags))
787         {
788            maxCell = cellTags.get(cellTags.size() - 1).getAttributeValue(SsmlXML.REF_ATT);
789         }
790*/
791         setDimension(new CellRange(minCell, maxCell));
792      }
793   }
794
795
796   //---------------------------------------------------------------------------
797   private XMLTag getMergeCellsTag()
798   {
799      if (null == mMergeCellsTag)
800      {
801         // Check if it has been added via addSubtag()...
802         mMergeCellsTag = getOptionalSubtagByName(SsmlXML.MERGE_CELLS);
803         if (null == mMergeCellsTag)
804         {
805            mMergeCellsTag = new XMLTag(SsmlXML.MERGE_CELLS);
806            addSubtag(mMergeCellsTag);
807         }
808      }
809
810      return mMergeCellsTag;
811   }
812
813}