View Javadoc
1   /*
2    * Metis: Java Data Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.metis.report;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
21  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
22  import io.github.tonywasher.joceanus.metis.exc.MetisIOException;
23  import org.w3c.dom.Document;
24  import org.w3c.dom.Element;
25  import org.w3c.dom.Node;
26  import org.w3c.dom.Text;
27  
28  import javax.xml.XMLConstants;
29  import javax.xml.parsers.DocumentBuilder;
30  import javax.xml.parsers.DocumentBuilderFactory;
31  
32  /**
33   * Build a report document.
34   */
35  public class MetisReportHTMLBuilder {
36      /**
37       * The class attribute.
38       */
39      private static final String ATTR_CLASS = "class";
40  
41      /**
42       * The id attribute.
43       */
44      protected static final String ATTR_ID = "id";
45  
46      /**
47       * The name attribute.
48       */
49      private static final String ATTR_NAME = "name";
50  
51      /**
52       * The href attribute.
53       */
54      private static final String ATTR_HREF = "href";
55  
56      /**
57       * The colspan attribute.
58       */
59      private static final String ATTR_COLSPAN = "colspan";
60  
61      /**
62       * The align attribute.
63       */
64      private static final String ATTR_ALIGN = "align";
65  
66      /**
67       * The align centre value.
68       */
69      private static final String ALIGN_CENTER = "center";
70  
71      /**
72       * Name of total table row class.
73       */
74      private static final String CLASS_TOTROW = "totalRow";
75  
76      /**
77       * Name of summary table row class.
78       */
79      private static final String CLASS_SUMMROW = "summRow";
80  
81      /**
82       * Name of alternate summary table row class.
83       */
84      private static final String CLASS_ALTSUMMROW = "altSummRow";
85  
86      /**
87       * Name of detailed summary row class.
88       */
89      private static final String CLASS_DTLSUMMROW = "dtlSummRow";
90  
91      /**
92       * Name of alternate detailed summary row class.
93       */
94      private static final String CLASS_ALTDTLSUMMROW = "altDtlSummRow";
95  
96      /**
97       * Name of subTable class.
98       */
99      private static final String CLASS_SUBTABLE = "subtable";
100 
101     /**
102      * Name of detail row class.
103      */
104     private static final String CLASS_DTLROW = "detailRow";
105 
106     /**
107      * Name of alternate detail row class.
108      */
109     private static final String CLASS_ALTDTLROW = "altDetailRow";
110 
111     /**
112      * Name of titleValue class.
113      */
114     private static final String CLASS_TITLEVALUE = "titleValue";
115 
116     /**
117      * Name of accordianValue class.
118      */
119     private static final String CLASS_ACCORDIANVALUE = "accordianValue";
120 
121     /**
122      * Name of linkValue class.
123      */
124     private static final String CLASS_LINKVALUE = "linkValue";
125 
126     /**
127      * Name of linkObject class.
128      */
129     private static final String CLASS_LINKOBJECT = "linkObject";
130 
131     /**
132      * Name of dataValue class.
133      */
134     private static final String CLASS_DATAVALUE = "dataValue";
135 
136     /**
137      * Name of negativeValue class.
138      */
139     private static final String CLASS_NEGVALUE = "negValue";
140 
141     /**
142      * The HTML element.
143      */
144     private static final String ELEMENT_HTML = "html";
145 
146     /**
147      * The body element.
148      */
149     private static final String ELEMENT_BODY = "body";
150 
151     /**
152      * The title element.
153      */
154     private static final String ELEMENT_TITLE = "h1";
155 
156     /**
157      * The subtitle element.
158      */
159     private static final String ELEMENT_SUBTITLE = "h2";
160 
161     /**
162      * The break element.
163      */
164     private static final String ELEMENT_BREAK = "br";
165 
166     /**
167      * The table element.
168      */
169     private static final String ELEMENT_TABLE = "table";
170 
171     /**
172      * The table header element.
173      */
174     private static final String ELEMENT_THDR = "thead";
175 
176     /**
177      * The table body element.
178      */
179     private static final String ELEMENT_TBODY = "tbody";
180 
181     /**
182      * The row element.
183      */
184     private static final String ELEMENT_ROW = "tr";
185 
186     /**
187      * The cell element.
188      */
189     private static final String ELEMENT_CELL = "td";
190 
191     /**
192      * The total cell element.
193      */
194     private static final String ELEMENT_TOTAL = "th";
195 
196     /**
197      * The link element.
198      */
199     private static final String ELEMENT_LINK = "a";
200 
201     /**
202      * The table reference header.
203      */
204     protected static final String REF_TAB = ELEMENT_TABLE;
205 
206     /**
207      * The id reference header.
208      */
209     protected static final String REF_ID = ATTR_ID;
210 
211     /**
212      * The filter reference header.
213      */
214     protected static final String REF_FILTER = "filter";
215 
216     /**
217      * The delayed reference header.
218      */
219     protected static final String REF_DELAY = "delay";
220 
221     /**
222      * The open accordion prefix.
223      */
224     protected static final String PFX_OPEN = "⯆ ";
225 
226     /**
227      * The collapsed accordion prefix.
228      */
229     private static final String PFX_COLLAPSED = "⯈ ";
230 
231     /**
232      * The document builder.
233      */
234     private final DocumentBuilder theBuilder;
235 
236     /**
237      * The document.
238      */
239     private Document theDocument;
240 
241     /**
242      * The data formatter.
243      */
244     private final OceanusDataFormatter theFormatter;
245 
246     /**
247      * Constructor.
248      *
249      * @param pFormatter the formatter
250      * @throws OceanusException on error
251      */
252     public MetisReportHTMLBuilder(final OceanusDataFormatter pFormatter) throws OceanusException {
253         /* Protect against exceptions */
254         try {
255             /* Store the formatter */
256             theFormatter = pFormatter;
257 
258             /* Create the document builder */
259             final DocumentBuilderFactory myDocFactory = DocumentBuilderFactory.newInstance();
260             myDocFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
261             myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
262             myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
263             theBuilder = myDocFactory.newDocumentBuilder();
264 
265         } catch (Exception e) {
266             throw new MetisIOException("Failed to create", e);
267         }
268     }
269 
270     /**
271      * Obtain the data formatter.
272      *
273      * @return the formatter
274      */
275     public OceanusDataFormatter getDataFormatter() {
276         return theFormatter;
277     }
278 
279     /**
280      * Obtain the document.
281      *
282      * @return the document
283      */
284     public Document getDocument() {
285         return theDocument;
286     }
287 
288     /**
289      * Create and append a standard empty cell.
290      *
291      * @param pControl the table control
292      */
293     public void makeValueCell(final MetisHTMLTable pControl) {
294         pControl.createNewCell(false);
295     }
296 
297     /**
298      * Create and append a standard empty total cell.
299      *
300      * @param pControl the table control
301      */
302     public void makeTotalCell(final MetisHTMLTable pControl) {
303         pControl.createNewCell(true);
304     }
305 
306     /**
307      * Create and append a standard empty title cell.
308      *
309      * @param pControl the table control
310      */
311     public void makeTitleCell(final MetisHTMLTable pControl) {
312         pControl.createNewCell(true);
313     }
314 
315     /**
316      * Create and append a standard cell with value.
317      *
318      * @param pControl the table control
319      * @param pValue   the value
320      */
321     public void makeValueCell(final MetisHTMLTable pControl,
322                               final Object pValue) {
323         final Element myCell = pControl.createNewCell(false);
324         setCellValue(myCell, pValue);
325     }
326 
327     /**
328      * Create and append a standard cell with value spanning 2 columns.
329      *
330      * @param pControl the table control
331      * @param pValue   the value
332      */
333     public void makeStretchedValueCell(final MetisHTMLTable pControl,
334                                        final Object pValue) {
335         final Element myCell = pControl.createNewCell(false);
336         setCellValue(myCell, pValue);
337         myCell.setAttribute(ATTR_COLSPAN, Integer.toString(2));
338     }
339 
340     /**
341      * Create and append a total cell with value.
342      *
343      * @param pControl the table control
344      * @param pValue   the value
345      */
346     public void makeTotalCell(final MetisHTMLTable pControl,
347                               final Object pValue) {
348         final Element myCell = pControl.createNewCell(true);
349         setCellValue(myCell, pValue);
350     }
351 
352     /**
353      * Create and append a standard total cell with value spanning 2 columns.
354      *
355      * @param pControl the table control
356      * @param pValue   the value
357      */
358     public void makeStretchedTotalCell(final MetisHTMLTable pControl,
359                                        final Object pValue) {
360         final Element myCell = pControl.createNewCell(true);
361         setCellValue(myCell, pValue);
362         myCell.setAttribute(ATTR_COLSPAN, Integer.toString(2));
363     }
364 
365     /**
366      * Create and append a title cell.
367      *
368      * @param pControl the table control
369      * @param pTitle   the title
370      */
371     public void makeTitleCell(final MetisHTMLTable pControl,
372                               final String pTitle) {
373         final Element myCell = pControl.createNewCell(true);
374         setCellTitle(myCell, pTitle);
375     }
376 
377     /**
378      * Create and append a standard title cell with title spanning 2 columns.
379      *
380      * @param pControl the table control
381      * @param pTitle   the title
382      */
383     public void makeStretchedTitleCell(final MetisHTMLTable pControl,
384                                        final String pTitle) {
385         final Element myCell = pControl.createNewCell(true);
386         setCellTitle(myCell, pTitle);
387         myCell.setAttribute(ATTR_COLSPAN, Integer.toString(2));
388     }
389 
390     /**
391      * Make Table link cell.
392      *
393      * @param pControl the table control
394      * @param pLink    the link table name
395      */
396     public void makeTableLinkCell(final MetisHTMLTable pControl,
397                                   final String pLink) {
398         makeTableLinkCell(pControl, pLink, pLink);
399     }
400 
401     /**
402      * Make Table link cell.
403      *
404      * @param pControl the table control
405      * @param pLink    the link table name
406      * @param pName    the link table display name
407      */
408     public void makeTableLinkCell(final MetisHTMLTable pControl,
409                                   final String pLink,
410                                   final String pName) {
411         /* Determine the id of the link */
412         final String myId = REF_ID
413                 + pLink;
414 
415         /* Create the header */
416         final Text myHeader = theDocument.createTextNode(PFX_OPEN);
417 
418         /* Create the cell */
419         final Element myCell = pControl.createNewCell(false);
420         final Element myLink = theDocument.createElement(ELEMENT_LINK);
421         myLink.setAttribute(ATTR_CLASS, CLASS_ACCORDIANVALUE);
422         myLink.setAttribute(ATTR_ID, myId);
423         myLink.setIdAttribute(ATTR_ID, true);
424         myLink.setAttribute(ATTR_NAME, myId);
425         myLink.setAttribute(ATTR_HREF, REF_TAB
426                 + pLink);
427         myLink.setTextContent(pName);
428         myCell.appendChild(myHeader);
429         myCell.appendChild(myLink);
430     }
431 
432     /**
433      * Make Delayed Table link cell.
434      *
435      * @param pControl the table control
436      * @param pLink    the link table name
437      */
438     public void makeDelayLinkCell(final MetisHTMLTable pControl,
439                                   final String pLink) {
440         makeDelayLinkCell(pControl, pLink, pLink);
441     }
442 
443     /**
444      * Make Delayed Table link cell.
445      *
446      * @param pControl the table control
447      * @param pLink    the link table name
448      * @param pName    the link table display name
449      */
450     public void makeDelayLinkCell(final MetisHTMLTable pControl,
451                                   final String pLink,
452                                   final String pName) {
453         /* Determine the id of the link */
454         final String myId = REF_ID
455                 + pLink;
456 
457         /* Create the header */
458         final Text myHeader = theDocument.createTextNode(PFX_COLLAPSED);
459 
460         /* Create the cell */
461         final Element myCell = pControl.createNewCell(false);
462         final Element myLink = theDocument.createElement(ELEMENT_LINK);
463         myLink.setAttribute(ATTR_CLASS, CLASS_ACCORDIANVALUE);
464         myLink.setAttribute(ATTR_ID, myId);
465         myLink.setIdAttribute(ATTR_ID, true);
466         myLink.setAttribute(ATTR_NAME, myId);
467         myLink.setAttribute(ATTR_HREF, REF_DELAY
468                 + pLink);
469         myLink.setTextContent(pName);
470         myCell.appendChild(myHeader);
471         myCell.appendChild(myLink);
472     }
473 
474     /**
475      * Make Filter link cell.
476      *
477      * @param pControl the table control
478      * @param pLink    the link table name
479      */
480     public void makeFilterLinkCell(final MetisHTMLTable pControl,
481                                    final String pLink) {
482         makeFilterLinkCell(pControl, pLink, pLink);
483     }
484 
485     /**
486      * Make Table link cell.
487      *
488      * @param pControl the table control
489      * @param pLink    the link table name
490      * @param pName    the link table display name
491      */
492     public void makeFilterLinkCell(final MetisHTMLTable pControl,
493                                    final String pLink,
494                                    final String pName) {
495         final Element myCell = pControl.createNewCell(false);
496         final Element myLink = theDocument.createElement(ELEMENT_LINK);
497         myLink.setAttribute(ATTR_CLASS, CLASS_LINKVALUE);
498         myLink.setAttribute(ATTR_HREF, REF_FILTER
499                 + pLink);
500         myLink.setTextContent(pName);
501         myCell.appendChild(myLink);
502     }
503 
504     /**
505      * Make Table link cell.
506      *
507      * @param pControl the table control
508      * @param pLink    the link table name
509      * @param pValue   the link table display value
510      */
511     public void makeFilterLinkCell(final MetisHTMLTable pControl,
512                                    final String pLink,
513                                    final Object pValue) {
514         final Element myCell = pControl.createNewCell(false);
515         myCell.setAttribute(ATTR_CLASS, CLASS_LINKOBJECT);
516         final Element myLink = theDocument.createElement(ELEMENT_LINK);
517         myLink.setAttribute(ATTR_HREF, REF_FILTER
518                 + pLink);
519         setCellValue(myLink, pValue);
520         myCell.appendChild(myLink);
521     }
522 
523     /**
524      * Set a cell value.
525      *
526      * @param pCell  the cell to set the value for
527      * @param pValue the value for the cell
528      */
529     private void setCellValue(final Element pCell,
530                               final Object pValue) {
531         Object myValue = pValue;
532         String myClass = CLASS_DATAVALUE;
533 
534         /* If this is an instance of Decimal */
535         if (myValue instanceof OceanusDecimal myDec) {
536             /* Ignore value if zero */
537             if (myDec.isZero()) {
538                 myValue = null;
539 
540                 /* Switch class if negative */
541             } else if (!myDec.isPositive()) {
542                 myClass = CLASS_NEGVALUE;
543             }
544         }
545 
546         /* Set class of cell */
547         pCell.setAttribute(ATTR_CLASS, myClass);
548 
549         /* Set value of cell */
550         if (myValue != null) {
551             pCell.setTextContent(theFormatter.formatObject(myValue));
552         }
553     }
554 
555     /**
556      * Set a cell title.
557      *
558      * @param pCell  the cell to set the value for
559      * @param pTitle the title for the cell
560      */
561     private static void setCellTitle(final Element pCell,
562                                      final String pTitle) {
563         /* Set class and content of cell */
564         pCell.setAttribute(ATTR_CLASS, CLASS_TITLEVALUE);
565         pCell.setTextContent(pTitle);
566     }
567 
568     /**
569      * Start a table header row.
570      *
571      * @param pControl the table control
572      */
573     public void startHdrRow(final MetisHTMLTable pControl) {
574         /* Create the row */
575         pControl.createNewRow(true);
576     }
577 
578     /**
579      * Start a table data row.
580      *
581      * @param pControl the table control
582      */
583     public void startRow(final MetisHTMLTable pControl) {
584         /* Create the row */
585         pControl.createNewRow(false);
586     }
587 
588     /**
589      * Start a table total row.
590      *
591      * @param pControl the table control
592      */
593     public void startTotalRow(final MetisHTMLTable pControl) {
594         /* Create the row */
595         pControl.createTotalRow();
596     }
597 
598     /**
599      * Start Report.
600      *
601      * @return the body
602      */
603     public Element startReport() {
604         /* Create the new document */
605         theDocument = theBuilder.newDocument();
606 
607         /* Create the standard structure */
608         final Element myHtml = theDocument.createElement(ELEMENT_HTML);
609         theDocument.appendChild(myHtml);
610         final Element myBody = theDocument.createElement(ELEMENT_BODY);
611         myHtml.appendChild(myBody);
612         return myBody;
613     }
614 
615     /**
616      * Make title.
617      *
618      * @param pBody  the document body
619      * @param pTitle the title
620      */
621     public void makeTitle(final Element pBody,
622                           final String pTitle) {
623         /* Create the title */
624         final Element myTitle = theDocument.createElement(ELEMENT_TITLE);
625         pBody.appendChild(myTitle);
626         myTitle.setTextContent(pTitle);
627     }
628 
629     /**
630      * Make two line title.
631      *
632      * @param pBody   the document body
633      * @param pTitle1 the first title
634      * @param pTitle2 the second title
635      */
636     public void makeTitle(final Element pBody,
637                           final String pTitle1,
638                           final String pTitle2) {
639         /* Create the title */
640         final Element myTitle = theDocument.createElement(ELEMENT_TITLE);
641         pBody.appendChild(myTitle);
642         Node myText = theDocument.createTextNode(pTitle1);
643         myTitle.appendChild(myText);
644         final Element myBreak = theDocument.createElement(ELEMENT_BREAK);
645         myTitle.appendChild(myBreak);
646         myText = theDocument.createTextNode(pTitle2);
647         myTitle.appendChild(myText);
648     }
649 
650     /**
651      * Make subtitle.
652      *
653      * @param pBody  the document body
654      * @param pTitle the title
655      */
656     public void makeSubTitle(final Element pBody,
657                              final String pTitle) {
658         /* Create the title */
659         final Element myTitle = theDocument.createElement(ELEMENT_SUBTITLE);
660         pBody.appendChild(myTitle);
661         myTitle.setTextContent(pTitle);
662     }
663 
664     /**
665      * Start Table.
666      *
667      * @param pBody the document body
668      * @return the table control
669      */
670     public MetisHTMLTable startTable(final Element pBody) {
671         /* Create the standard structure */
672         final Element myTable = theDocument.createElement(ELEMENT_TABLE);
673         pBody.appendChild(myTable);
674         myTable.setAttribute(ATTR_ALIGN, ALIGN_CENTER);
675 
676         /* Create the table control */
677         return new MetisHTMLTable(myTable);
678     }
679 
680     /**
681      * Create an embedded table.
682      *
683      * @param pParent the parent element
684      * @return the new table
685      */
686     public MetisHTMLTable createEmbeddedTable(final MetisHTMLTable pParent) {
687         /* Create the table */
688         final Element myTable = theDocument.createElement(ELEMENT_TABLE);
689         myTable.setAttribute(ATTR_CLASS, CLASS_SUBTABLE);
690         myTable.setAttribute(ATTR_ALIGN, ALIGN_CENTER);
691 
692         /* Create the table control */
693         return new MetisHTMLTable(myTable, pParent);
694     }
695 
696     /**
697      * Embed a table into the document.
698      *
699      * @param pTable the table to embed
700      * @param pTitle the title of the table
701      */
702     public void embedTable(final MetisHTMLTable pTable,
703                            final String pTitle) {
704         /* Access body element */
705         final MetisHTMLTable myParent = pTable.getParent();
706         final Element myLink = getLinkRow(pTitle);
707         final Node myNextRow = myLink.getNextSibling();
708 
709         /* Create the row */
710         final Element myRow = theDocument.createElement(ELEMENT_ROW);
711         myRow.setAttribute(ATTR_ID, REF_TAB
712                 + pTitle);
713         myRow.setIdAttribute(ATTR_ID, true);
714         final Element myCell = theDocument.createElement(ELEMENT_CELL);
715         myRow.appendChild(myCell);
716         myCell.setAttribute(ATTR_COLSPAN, Integer.toString(myParent.getNumCols()));
717         myCell.appendChild(pTable.getTable());
718 
719         /* Insert into the correct place in the document */
720         final Node myTop = myLink.getParentNode();
721         if (myNextRow == null) {
722             myTop.appendChild(myRow);
723         } else {
724             myTop.insertBefore(myRow, myNextRow);
725         }
726 
727         /* Adjust prefix of owning link */
728         setPrefix(myLink, true);
729     }
730 
731     /**
732      * Embed a table into the document.
733      *
734      * @param pTable the table to embed
735      */
736     public void embedTable(final MetisHTMLTable pTable) {
737         /* Access body element */
738         final MetisHTMLTable myParent = pTable.getParent();
739         final Element myBody = myParent.getTableBody();
740 
741         /* Create the row */
742         final Element myRow = theDocument.createElement(ELEMENT_ROW);
743         final Element myCell = theDocument.createElement(ELEMENT_CELL);
744         myRow.appendChild(myCell);
745         myCell.setAttribute(ATTR_COLSPAN, Integer.toString(myParent.getNumCols()));
746         myCell.appendChild(pTable.getTable());
747 
748         /* Insert into the correct place in the document */
749         myBody.appendChild(myRow);
750     }
751 
752     /**
753      * Obtain the row that a link is in.
754      *
755      * @param pTitle the title of the link
756      * @return the row that contains the link
757      */
758     protected Element getLinkRow(final String pTitle) {
759         /* Determine the id of the link */
760         final String myId = REF_ID
761                 + pTitle;
762 
763         /* Locate the cell element */
764         final Element myLink = theDocument.getElementById(myId);
765         final Node myCell = myLink.getParentNode();
766         final Node myParent = myCell.getParentNode();
767 
768         /* Update the link to be a table reference */
769         myLink.setAttribute(ATTR_HREF, REF_TAB
770                 + pTitle);
771 
772         /* Cast result to element */
773         return myParent instanceof Element myElement
774                 ? myElement
775                 : null;
776     }
777 
778     /**
779      * Set prefix for owning link.
780      *
781      * @param pOwner the owning link
782      * @param pOpen  is the link open true/false
783      */
784     void setPrefix(final Node pOwner,
785                    final boolean pOpen) {
786         /* Adjust prefix of owning link */
787         pOwner.getFirstChild().getFirstChild().setTextContent(pOpen ? PFX_OPEN : PFX_COLLAPSED);
788     }
789 
790     /**
791      * Table control class.
792      */
793     public final class MetisHTMLTable {
794         /**
795          * Table class type.
796          */
797         private final TableClass theClass;
798 
799         /**
800          * The table parent.
801          */
802         private final MetisHTMLTable theParent;
803 
804         /**
805          * The table element.
806          */
807         private final Element theTable;
808 
809         /**
810          * The table header element.
811          */
812         private Element theHeader;
813 
814         /**
815          * The table body element.
816          */
817         private Element theBody;
818 
819         /**
820          * The current row.
821          */
822         private Element theRow;
823 
824         /**
825          * Was the last row an odd row.
826          */
827         private boolean wasOdd;
828 
829         /**
830          * Current # of columns.
831          */
832         private int numCols = -1;
833 
834         /**
835          * Max columns.
836          */
837         private int maxCols;
838 
839         /**
840          * Constructor.
841          *
842          * @param pTable  the table element
843          * @param pParent the parent table.
844          */
845         private MetisHTMLTable(final Element pTable,
846                                final MetisHTMLTable pParent) {
847             /* Store parameters */
848             theTable = pTable;
849             theParent = pParent;
850             theClass = pParent.getNextTableClass();
851         }
852 
853         /**
854          * Constructor.
855          *
856          * @param pTable the table element
857          */
858         private MetisHTMLTable(final Element pTable) {
859             /* Store parameters */
860             theTable = pTable;
861             theParent = null;
862             theClass = TableClass.SUMMARY;
863         }
864 
865         /**
866          * Obtain the table header.
867          *
868          * @return the header
869          */
870         private Element getTableHeader() {
871             /* If we have not yet created the header */
872             if (theHeader == null) {
873                 /* Create the header */
874                 theHeader = theDocument.createElement(ELEMENT_THDR);
875 
876                 /* Insert before body */
877                 if (theBody == null) {
878                     theTable.appendChild(theHeader);
879                 } else {
880                     theTable.insertBefore(theHeader, theBody);
881                 }
882             }
883             return theHeader;
884         }
885 
886         /**
887          * Obtain the table body.
888          *
889          * @return the body
890          */
891         private Element getTableBody() {
892             /* If we have not yet created the body */
893             if (theBody == null) {
894                 /* Create the body */
895                 theBody = theDocument.createElement(ELEMENT_TBODY);
896                 theTable.appendChild(theBody);
897             }
898             return theBody;
899         }
900 
901         /**
902          * Obtain the number of columns.
903          *
904          * @return the number of columns
905          */
906         private int getNumCols() {
907             return maxCols;
908         }
909 
910         /**
911          * Obtain the parent table.
912          *
913          * @return the parent
914          */
915         private MetisHTMLTable getParent() {
916             return theParent;
917         }
918 
919         /**
920          * Obtain the table.
921          *
922          * @return the table
923          */
924         private Element getTable() {
925             return theTable;
926         }
927 
928         /**
929          * Obtain the next class name.
930          *
931          * @return the next class name.
932          */
933         private String getNextRowClass() {
934             /* Switch flag */
935             wasOdd = !wasOdd;
936 
937             /* Return the class name */
938             return wasOdd
939                     ? theClass.getOddClass()
940                     : theClass.getEvenClass();
941         }
942 
943         /**
944          * Obtain the next table name.
945          *
946          * @return the next class.
947          */
948         private TableClass getNextTableClass() {
949             /* Return the class */
950             return theClass.getNextClass();
951         }
952 
953         /**
954          * Create a new row.
955          */
956         private void createTotalRow() {
957             /* Determine the parent */
958             final Element myParent = getTableBody();
959 
960             /* Create the row */
961             theRow = theDocument.createElement(ELEMENT_ROW);
962             myParent.appendChild(theRow);
963             theRow.setAttribute(ATTR_CLASS, CLASS_TOTROW);
964 
965             /* Adjust # of columns */
966             numCols = 0;
967         }
968 
969         /**
970          * Create a new row.
971          *
972          * @param bHdr use header rather than body (true/false)
973          */
974         private void createNewRow(final boolean bHdr) {
975             /* Determine the parent */
976             final Element myParent = bHdr
977                     ? getTableHeader()
978                     : getTableBody();
979 
980             /* Create the row */
981             theRow = theDocument.createElement(ELEMENT_ROW);
982             myParent.appendChild(theRow);
983             theRow.setAttribute(ATTR_CLASS, bHdr
984                     ? CLASS_TOTROW
985                     : getNextRowClass());
986 
987             /* Adjust # of columns */
988             numCols = 0;
989         }
990 
991         /**
992          * Create a new cell in the current row.
993          *
994          * @param bTotal create total cell (true/false)
995          * @return the new cell
996          */
997         private Element createNewCell(final boolean bTotal) {
998             /* Determine the cell type */
999             final String myCellType = bTotal
1000                     ? ELEMENT_TOTAL
1001                     : ELEMENT_CELL;
1002 
1003             /* Adjust column # */
1004             numCols++;
1005             if (numCols >= maxCols) {
1006                 maxCols = numCols;
1007             }
1008 
1009             /* Create the cell in the current row */
1010             final Element myCell = theDocument.createElement(myCellType);
1011             theRow.appendChild(myCell);
1012             return myCell;
1013         }
1014 
1015     }
1016 
1017     /**
1018      * Table class.
1019      */
1020     public enum TableClass {
1021         /**
1022          * Summary.
1023          */
1024         SUMMARY,
1025 
1026         /**
1027          * DetailedSummary.
1028          */
1029         DETAILEDSUMMARY,
1030 
1031         /**
1032          * Detail.
1033          */
1034         DETAIL;
1035 
1036         /**
1037          * Obtain the odd class name.
1038          *
1039          * @return the class name
1040          */
1041         private String getOddClass() {
1042             switch (this) {
1043                 case SUMMARY:
1044                     return CLASS_SUMMROW;
1045                 case DETAILEDSUMMARY:
1046                     return CLASS_DTLSUMMROW;
1047                 case DETAIL:
1048                     return CLASS_DTLROW;
1049                 default:
1050                     return null;
1051             }
1052         }
1053 
1054         /**
1055          * Obtain the even class name.
1056          *
1057          * @return the class name
1058          */
1059         private String getEvenClass() {
1060             switch (this) {
1061                 case SUMMARY:
1062                     return CLASS_ALTSUMMROW;
1063                 case DETAILEDSUMMARY:
1064                     return CLASS_ALTDTLSUMMROW;
1065                 case DETAIL:
1066                     return CLASS_ALTDTLROW;
1067                 default:
1068                     return null;
1069             }
1070         }
1071 
1072         /**
1073          * Obtain the embedded TableClass.
1074          *
1075          * @return the class name
1076          */
1077         private TableClass getNextClass() {
1078             switch (this) {
1079                 case SUMMARY:
1080                     return DETAILEDSUMMARY;
1081                 case DETAILEDSUMMARY:
1082                 case DETAIL:
1083                     return DETAIL;
1084                 default:
1085                     return null;
1086             }
1087         }
1088     }
1089 }