1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
34
35 public class MetisReportHTMLBuilder {
36
37
38
39 private static final String ATTR_CLASS = "class";
40
41
42
43
44 protected static final String ATTR_ID = "id";
45
46
47
48
49 private static final String ATTR_NAME = "name";
50
51
52
53
54 private static final String ATTR_HREF = "href";
55
56
57
58
59 private static final String ATTR_COLSPAN = "colspan";
60
61
62
63
64 private static final String ATTR_ALIGN = "align";
65
66
67
68
69 private static final String ALIGN_CENTER = "center";
70
71
72
73
74 private static final String CLASS_TOTROW = "totalRow";
75
76
77
78
79 private static final String CLASS_SUMMROW = "summRow";
80
81
82
83
84 private static final String CLASS_ALTSUMMROW = "altSummRow";
85
86
87
88
89 private static final String CLASS_DTLSUMMROW = "dtlSummRow";
90
91
92
93
94 private static final String CLASS_ALTDTLSUMMROW = "altDtlSummRow";
95
96
97
98
99 private static final String CLASS_SUBTABLE = "subtable";
100
101
102
103
104 private static final String CLASS_DTLROW = "detailRow";
105
106
107
108
109 private static final String CLASS_ALTDTLROW = "altDetailRow";
110
111
112
113
114 private static final String CLASS_TITLEVALUE = "titleValue";
115
116
117
118
119 private static final String CLASS_ACCORDIANVALUE = "accordianValue";
120
121
122
123
124 private static final String CLASS_LINKVALUE = "linkValue";
125
126
127
128
129 private static final String CLASS_LINKOBJECT = "linkObject";
130
131
132
133
134 private static final String CLASS_DATAVALUE = "dataValue";
135
136
137
138
139 private static final String CLASS_NEGVALUE = "negValue";
140
141
142
143
144 private static final String ELEMENT_HTML = "html";
145
146
147
148
149 private static final String ELEMENT_BODY = "body";
150
151
152
153
154 private static final String ELEMENT_TITLE = "h1";
155
156
157
158
159 private static final String ELEMENT_SUBTITLE = "h2";
160
161
162
163
164 private static final String ELEMENT_BREAK = "br";
165
166
167
168
169 private static final String ELEMENT_TABLE = "table";
170
171
172
173
174 private static final String ELEMENT_THDR = "thead";
175
176
177
178
179 private static final String ELEMENT_TBODY = "tbody";
180
181
182
183
184 private static final String ELEMENT_ROW = "tr";
185
186
187
188
189 private static final String ELEMENT_CELL = "td";
190
191
192
193
194 private static final String ELEMENT_TOTAL = "th";
195
196
197
198
199 private static final String ELEMENT_LINK = "a";
200
201
202
203
204 protected static final String REF_TAB = ELEMENT_TABLE;
205
206
207
208
209 protected static final String REF_ID = ATTR_ID;
210
211
212
213
214 protected static final String REF_FILTER = "filter";
215
216
217
218
219 protected static final String REF_DELAY = "delay";
220
221
222
223
224 protected static final String PFX_OPEN = "⯆ ";
225
226
227
228
229 private static final String PFX_COLLAPSED = "⯈ ";
230
231
232
233
234 private final DocumentBuilder theBuilder;
235
236
237
238
239 private Document theDocument;
240
241
242
243
244 private final OceanusDataFormatter theFormatter;
245
246
247
248
249
250
251
252 public MetisReportHTMLBuilder(final OceanusDataFormatter pFormatter) throws OceanusException {
253
254 try {
255
256 theFormatter = pFormatter;
257
258
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
272
273
274
275 public OceanusDataFormatter getDataFormatter() {
276 return theFormatter;
277 }
278
279
280
281
282
283
284 public Document getDocument() {
285 return theDocument;
286 }
287
288
289
290
291
292
293 public void makeValueCell(final MetisHTMLTable pControl) {
294 pControl.createNewCell(false);
295 }
296
297
298
299
300
301
302 public void makeTotalCell(final MetisHTMLTable pControl) {
303 pControl.createNewCell(true);
304 }
305
306
307
308
309
310
311 public void makeTitleCell(final MetisHTMLTable pControl) {
312 pControl.createNewCell(true);
313 }
314
315
316
317
318
319
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
329
330
331
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
342
343
344
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
354
355
356
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
367
368
369
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
379
380
381
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
392
393
394
395
396 public void makeTableLinkCell(final MetisHTMLTable pControl,
397 final String pLink) {
398 makeTableLinkCell(pControl, pLink, pLink);
399 }
400
401
402
403
404
405
406
407
408 public void makeTableLinkCell(final MetisHTMLTable pControl,
409 final String pLink,
410 final String pName) {
411
412 final String myId = REF_ID
413 + pLink;
414
415
416 final Text myHeader = theDocument.createTextNode(PFX_OPEN);
417
418
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
434
435
436
437
438 public void makeDelayLinkCell(final MetisHTMLTable pControl,
439 final String pLink) {
440 makeDelayLinkCell(pControl, pLink, pLink);
441 }
442
443
444
445
446
447
448
449
450 public void makeDelayLinkCell(final MetisHTMLTable pControl,
451 final String pLink,
452 final String pName) {
453
454 final String myId = REF_ID
455 + pLink;
456
457
458 final Text myHeader = theDocument.createTextNode(PFX_COLLAPSED);
459
460
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
476
477
478
479
480 public void makeFilterLinkCell(final MetisHTMLTable pControl,
481 final String pLink) {
482 makeFilterLinkCell(pControl, pLink, pLink);
483 }
484
485
486
487
488
489
490
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
506
507
508
509
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
525
526
527
528
529 private void setCellValue(final Element pCell,
530 final Object pValue) {
531 Object myValue = pValue;
532 String myClass = CLASS_DATAVALUE;
533
534
535 if (myValue instanceof OceanusDecimal myDec) {
536
537 if (myDec.isZero()) {
538 myValue = null;
539
540
541 } else if (!myDec.isPositive()) {
542 myClass = CLASS_NEGVALUE;
543 }
544 }
545
546
547 pCell.setAttribute(ATTR_CLASS, myClass);
548
549
550 if (myValue != null) {
551 pCell.setTextContent(theFormatter.formatObject(myValue));
552 }
553 }
554
555
556
557
558
559
560
561 private static void setCellTitle(final Element pCell,
562 final String pTitle) {
563
564 pCell.setAttribute(ATTR_CLASS, CLASS_TITLEVALUE);
565 pCell.setTextContent(pTitle);
566 }
567
568
569
570
571
572
573 public void startHdrRow(final MetisHTMLTable pControl) {
574
575 pControl.createNewRow(true);
576 }
577
578
579
580
581
582
583 public void startRow(final MetisHTMLTable pControl) {
584
585 pControl.createNewRow(false);
586 }
587
588
589
590
591
592
593 public void startTotalRow(final MetisHTMLTable pControl) {
594
595 pControl.createTotalRow();
596 }
597
598
599
600
601
602
603 public Element startReport() {
604
605 theDocument = theBuilder.newDocument();
606
607
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
617
618
619
620
621 public void makeTitle(final Element pBody,
622 final String pTitle) {
623
624 final Element myTitle = theDocument.createElement(ELEMENT_TITLE);
625 pBody.appendChild(myTitle);
626 myTitle.setTextContent(pTitle);
627 }
628
629
630
631
632
633
634
635
636 public void makeTitle(final Element pBody,
637 final String pTitle1,
638 final String pTitle2) {
639
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
652
653
654
655
656 public void makeSubTitle(final Element pBody,
657 final String pTitle) {
658
659 final Element myTitle = theDocument.createElement(ELEMENT_SUBTITLE);
660 pBody.appendChild(myTitle);
661 myTitle.setTextContent(pTitle);
662 }
663
664
665
666
667
668
669
670 public MetisHTMLTable startTable(final Element pBody) {
671
672 final Element myTable = theDocument.createElement(ELEMENT_TABLE);
673 pBody.appendChild(myTable);
674 myTable.setAttribute(ATTR_ALIGN, ALIGN_CENTER);
675
676
677 return new MetisHTMLTable(myTable);
678 }
679
680
681
682
683
684
685
686 public MetisHTMLTable createEmbeddedTable(final MetisHTMLTable pParent) {
687
688 final Element myTable = theDocument.createElement(ELEMENT_TABLE);
689 myTable.setAttribute(ATTR_CLASS, CLASS_SUBTABLE);
690 myTable.setAttribute(ATTR_ALIGN, ALIGN_CENTER);
691
692
693 return new MetisHTMLTable(myTable, pParent);
694 }
695
696
697
698
699
700
701
702 public void embedTable(final MetisHTMLTable pTable,
703 final String pTitle) {
704
705 final MetisHTMLTable myParent = pTable.getParent();
706 final Element myLink = getLinkRow(pTitle);
707 final Node myNextRow = myLink.getNextSibling();
708
709
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
720 final Node myTop = myLink.getParentNode();
721 if (myNextRow == null) {
722 myTop.appendChild(myRow);
723 } else {
724 myTop.insertBefore(myRow, myNextRow);
725 }
726
727
728 setPrefix(myLink, true);
729 }
730
731
732
733
734
735
736 public void embedTable(final MetisHTMLTable pTable) {
737
738 final MetisHTMLTable myParent = pTable.getParent();
739 final Element myBody = myParent.getTableBody();
740
741
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
749 myBody.appendChild(myRow);
750 }
751
752
753
754
755
756
757
758 protected Element getLinkRow(final String pTitle) {
759
760 final String myId = REF_ID
761 + pTitle;
762
763
764 final Element myLink = theDocument.getElementById(myId);
765 final Node myCell = myLink.getParentNode();
766 final Node myParent = myCell.getParentNode();
767
768
769 myLink.setAttribute(ATTR_HREF, REF_TAB
770 + pTitle);
771
772
773 return myParent instanceof Element myElement
774 ? myElement
775 : null;
776 }
777
778
779
780
781
782
783
784 void setPrefix(final Node pOwner,
785 final boolean pOpen) {
786
787 pOwner.getFirstChild().getFirstChild().setTextContent(pOpen ? PFX_OPEN : PFX_COLLAPSED);
788 }
789
790
791
792
793 public final class MetisHTMLTable {
794
795
796
797 private final TableClass theClass;
798
799
800
801
802 private final MetisHTMLTable theParent;
803
804
805
806
807 private final Element theTable;
808
809
810
811
812 private Element theHeader;
813
814
815
816
817 private Element theBody;
818
819
820
821
822 private Element theRow;
823
824
825
826
827 private boolean wasOdd;
828
829
830
831
832 private int numCols = -1;
833
834
835
836
837 private int maxCols;
838
839
840
841
842
843
844
845 private MetisHTMLTable(final Element pTable,
846 final MetisHTMLTable pParent) {
847
848 theTable = pTable;
849 theParent = pParent;
850 theClass = pParent.getNextTableClass();
851 }
852
853
854
855
856
857
858 private MetisHTMLTable(final Element pTable) {
859
860 theTable = pTable;
861 theParent = null;
862 theClass = TableClass.SUMMARY;
863 }
864
865
866
867
868
869
870 private Element getTableHeader() {
871
872 if (theHeader == null) {
873
874 theHeader = theDocument.createElement(ELEMENT_THDR);
875
876
877 if (theBody == null) {
878 theTable.appendChild(theHeader);
879 } else {
880 theTable.insertBefore(theHeader, theBody);
881 }
882 }
883 return theHeader;
884 }
885
886
887
888
889
890
891 private Element getTableBody() {
892
893 if (theBody == null) {
894
895 theBody = theDocument.createElement(ELEMENT_TBODY);
896 theTable.appendChild(theBody);
897 }
898 return theBody;
899 }
900
901
902
903
904
905
906 private int getNumCols() {
907 return maxCols;
908 }
909
910
911
912
913
914
915 private MetisHTMLTable getParent() {
916 return theParent;
917 }
918
919
920
921
922
923
924 private Element getTable() {
925 return theTable;
926 }
927
928
929
930
931
932
933 private String getNextRowClass() {
934
935 wasOdd = !wasOdd;
936
937
938 return wasOdd
939 ? theClass.getOddClass()
940 : theClass.getEvenClass();
941 }
942
943
944
945
946
947
948 private TableClass getNextTableClass() {
949
950 return theClass.getNextClass();
951 }
952
953
954
955
956 private void createTotalRow() {
957
958 final Element myParent = getTableBody();
959
960
961 theRow = theDocument.createElement(ELEMENT_ROW);
962 myParent.appendChild(theRow);
963 theRow.setAttribute(ATTR_CLASS, CLASS_TOTROW);
964
965
966 numCols = 0;
967 }
968
969
970
971
972
973
974 private void createNewRow(final boolean bHdr) {
975
976 final Element myParent = bHdr
977 ? getTableHeader()
978 : getTableBody();
979
980
981 theRow = theDocument.createElement(ELEMENT_ROW);
982 myParent.appendChild(theRow);
983 theRow.setAttribute(ATTR_CLASS, bHdr
984 ? CLASS_TOTROW
985 : getNextRowClass());
986
987
988 numCols = 0;
989 }
990
991
992
993
994
995
996
997 private Element createNewCell(final boolean bTotal) {
998
999 final String myCellType = bTotal
1000 ? ELEMENT_TOTAL
1001 : ELEMENT_CELL;
1002
1003
1004 numCols++;
1005 if (numCols >= maxCols) {
1006 maxCols = numCols;
1007 }
1008
1009
1010 final Element myCell = theDocument.createElement(myCellType);
1011 theRow.appendChild(myCell);
1012 return myCell;
1013 }
1014
1015 }
1016
1017
1018
1019
1020 public enum TableClass {
1021
1022
1023
1024 SUMMARY,
1025
1026
1027
1028
1029 DETAILEDSUMMARY,
1030
1031
1032
1033
1034 DETAIL;
1035
1036
1037
1038
1039
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
1056
1057
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
1074
1075
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 }