1 package org.apache.maven.doxia.sink.impl;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.PrintWriter;
23 import java.io.StringWriter;
24 import java.io.Writer;
25 import java.util.ArrayList;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.Stack;
33
34 import javax.swing.text.MutableAttributeSet;
35 import javax.swing.text.html.HTML.Attribute;
36 import javax.swing.text.html.HTML.Tag;
37
38 import org.apache.maven.doxia.markup.HtmlMarkup;
39 import org.apache.maven.doxia.markup.Markup;
40 import org.apache.maven.doxia.sink.Sink;
41 import org.apache.maven.doxia.sink.SinkEventAttributes;
42 import org.apache.maven.doxia.util.DoxiaUtils;
43 import org.apache.maven.doxia.util.HtmlTools;
44
45 import org.codehaus.plexus.util.StringUtils;
46 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50
51
52
53
54
55
56
57 public class XhtmlBaseSink
58 extends AbstractXmlSink
59 implements HtmlMarkup
60 {
61 private static final Logger LOGGER = LoggerFactory.getLogger( XhtmlBaseSink.class );
62
63
64
65
66
67
68 private final PrintWriter writer;
69
70
71 private StringBuffer textBuffer = new StringBuffer();
72
73
74 private boolean headFlag;
75
76
77 private boolean figureCaptionFlag;
78
79
80 private boolean paragraphFlag;
81
82
83 private boolean verbatimFlag;
84
85
86 private final LinkedList<int[]> cellJustifStack;
87
88
89 private final LinkedList<Boolean> isCellJustifStack;
90
91
92 private final LinkedList<Integer> cellCountStack;
93
94
95 private boolean evenTableRow = true;
96
97
98 private final LinkedList<StringWriter> tableContentWriterStack;
99
100 private final LinkedList<StringWriter> tableCaptionWriterStack;
101
102 private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
103
104
105 private final LinkedList<String> tableCaptionStack;
106
107
108 protected MutableAttributeSet tableAttributes;
109
110
111 private boolean legacyFigure;
112
113
114 private boolean legacyFigureCaption;
115
116
117 private boolean inFigure;
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 protected boolean tableRows = false;
133
134
135 protected Stack<List<Tag>> inlineStack = new Stack<>();
136
137
138
139
140
141
142
143
144
145
146 public XhtmlBaseSink( Writer out )
147 {
148 this.writer = new PrintWriter( out );
149
150 this.cellJustifStack = new LinkedList<>();
151 this.isCellJustifStack = new LinkedList<>();
152 this.cellCountStack = new LinkedList<>();
153 this.tableContentWriterStack = new LinkedList<>();
154 this.tableCaptionWriterStack = new LinkedList<>();
155 this.tableCaptionXMLWriterStack = new LinkedList<>();
156 this.tableCaptionStack = new LinkedList<>();
157
158 init();
159 }
160
161
162
163
164
165
166
167
168
169
170 protected StringBuffer getTextBuffer()
171 {
172 return this.textBuffer;
173 }
174
175
176
177
178
179
180 protected void setHeadFlag( boolean headFlag )
181 {
182 this.headFlag = headFlag;
183 }
184
185
186
187
188
189
190 protected boolean isHeadFlag()
191 {
192 return this.headFlag ;
193 }
194
195
196
197
198
199
200 protected void setVerbatimFlag( boolean verb )
201 {
202 this.verbatimFlag = verb;
203 }
204
205
206
207
208
209
210 protected boolean isVerbatimFlag()
211 {
212 return this.verbatimFlag ;
213 }
214
215
216
217
218
219
220 protected void setCellJustif( int[] justif )
221 {
222 this.cellJustifStack.addLast( justif );
223 this.isCellJustifStack.addLast( Boolean.TRUE );
224 }
225
226
227
228
229
230
231 protected int[] getCellJustif()
232 {
233 return this.cellJustifStack.getLast();
234 }
235
236
237
238
239
240
241 protected void setCellCount( int count )
242 {
243 this.cellCountStack.addLast( count );
244 }
245
246
247
248
249
250
251 protected int getCellCount()
252 {
253 return Integer.parseInt( this.cellCountStack.getLast().toString() );
254 }
255
256
257 @Override
258 protected void init()
259 {
260 super.init();
261
262 resetTextBuffer();
263
264 this.cellJustifStack.clear();
265 this.isCellJustifStack.clear();
266 this.cellCountStack.clear();
267 this.tableContentWriterStack.clear();
268 this.tableCaptionWriterStack.clear();
269 this.tableCaptionXMLWriterStack.clear();
270 this.tableCaptionStack.clear();
271
272 this.headFlag = false;
273 this.figureCaptionFlag = false;
274 this.paragraphFlag = false;
275 this.verbatimFlag = false;
276
277 this.evenTableRow = true;
278 this.tableAttributes = null;
279 this.legacyFigure = false;
280 this.legacyFigureCaption = false;
281 this.inFigure = false;
282 this.tableRows = false;
283 }
284
285
286
287
288 protected void resetTextBuffer()
289 {
290 this.textBuffer = new StringBuffer();
291 }
292
293
294
295
296
297
298 @Override
299 public void section( int level, SinkEventAttributes attributes )
300 {
301 onSection( level, attributes );
302 }
303
304
305 @Override
306 public void sectionTitle( int level, SinkEventAttributes attributes )
307 {
308 onSectionTitle( level, attributes );
309 }
310
311
312 @Override
313 public void sectionTitle_( int level )
314 {
315 onSectionTitle_( level );
316 }
317
318
319 @Override
320 public void section_( int level )
321 {
322 onSection_( level );
323 }
324
325
326 @Override
327 public void section1()
328 {
329 onSection( SECTION_LEVEL_1, null );
330 }
331
332
333 @Override
334 public void sectionTitle1()
335 {
336 onSectionTitle( SECTION_LEVEL_1, null );
337 }
338
339
340 @Override
341 public void sectionTitle1_()
342 {
343 onSectionTitle_( SECTION_LEVEL_1 );
344 }
345
346
347 @Override
348 public void section1_()
349 {
350 onSection_( SECTION_LEVEL_1 );
351 }
352
353
354 @Override
355 public void section2()
356 {
357 onSection( SECTION_LEVEL_2, null );
358 }
359
360
361 @Override
362 public void sectionTitle2()
363 {
364 onSectionTitle( SECTION_LEVEL_2, null );
365 }
366
367
368 @Override
369 public void sectionTitle2_()
370 {
371 onSectionTitle_( SECTION_LEVEL_2 );
372 }
373
374
375 @Override
376 public void section2_()
377 {
378 onSection_( SECTION_LEVEL_2 );
379 }
380
381
382 @Override
383 public void section3()
384 {
385 onSection( SECTION_LEVEL_3, null );
386 }
387
388
389 @Override
390 public void sectionTitle3()
391 {
392 onSectionTitle( SECTION_LEVEL_3, null );
393 }
394
395
396 @Override
397 public void sectionTitle3_()
398 {
399 onSectionTitle_( SECTION_LEVEL_3 );
400 }
401
402
403 @Override
404 public void section3_()
405 {
406 onSection_( SECTION_LEVEL_3 );
407 }
408
409
410 @Override
411 public void section4()
412 {
413 onSection( SECTION_LEVEL_4, null );
414 }
415
416
417 @Override
418 public void sectionTitle4()
419 {
420 onSectionTitle( SECTION_LEVEL_4, null );
421 }
422
423
424 @Override
425 public void sectionTitle4_()
426 {
427 onSectionTitle_( SECTION_LEVEL_4 );
428 }
429
430
431 @Override
432 public void section4_()
433 {
434 onSection_( SECTION_LEVEL_4 );
435 }
436
437
438 @Override
439 public void section5()
440 {
441 onSection( SECTION_LEVEL_5, null );
442 }
443
444
445 @Override
446 public void sectionTitle5()
447 {
448 onSectionTitle( SECTION_LEVEL_5, null );
449 }
450
451
452 @Override
453 public void sectionTitle5_()
454 {
455 onSectionTitle_( SECTION_LEVEL_5 );
456 }
457
458
459 @Override
460 public void section5_()
461 {
462 onSection_( SECTION_LEVEL_5 );
463 }
464
465
466
467
468
469
470
471
472 protected void onSection( int depth, SinkEventAttributes attributes )
473 {
474 if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
475 {
476 MutableAttributeSet att = new SinkEventAttributeSet();
477 att.addAttribute( Attribute.CLASS, "section" );
478
479 att.addAttributes( SinkUtils.filterAttributes(
480 attributes, SinkUtils.SINK_BASE_ATTRIBUTES ) );
481
482 writeStartTag( HtmlMarkup.DIV, att );
483 }
484 }
485
486
487
488
489
490
491
492 protected void onSection_( int depth )
493 {
494 if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
495 {
496 writeEndTag( HtmlMarkup.DIV );
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511 protected void onSectionTitle( int depth, SinkEventAttributes attributes )
512 {
513 MutableAttributeSet atts = SinkUtils.filterAttributes(
514 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
515
516 if ( depth == SECTION_LEVEL_1 )
517 {
518 writeStartTag( HtmlMarkup.H2, atts );
519 }
520 else if ( depth == SECTION_LEVEL_2 )
521 {
522 writeStartTag( HtmlMarkup.H3, atts );
523 }
524 else if ( depth == SECTION_LEVEL_3 )
525 {
526 writeStartTag( HtmlMarkup.H4, atts );
527 }
528 else if ( depth == SECTION_LEVEL_4 )
529 {
530 writeStartTag( HtmlMarkup.H5, atts );
531 }
532 else if ( depth == SECTION_LEVEL_5 )
533 {
534 writeStartTag( HtmlMarkup.H6, atts );
535 }
536 }
537
538
539
540
541
542
543
544
545
546
547
548 protected void onSectionTitle_( int depth )
549 {
550 if ( depth == SECTION_LEVEL_1 )
551 {
552 writeEndTag( HtmlMarkup.H2 );
553 }
554 else if ( depth == SECTION_LEVEL_2 )
555 {
556 writeEndTag( HtmlMarkup.H3 );
557 }
558 else if ( depth == SECTION_LEVEL_3 )
559 {
560 writeEndTag( HtmlMarkup.H4 );
561 }
562 else if ( depth == SECTION_LEVEL_4 )
563 {
564 writeEndTag( HtmlMarkup.H5 );
565 }
566 else if ( depth == SECTION_LEVEL_5 )
567 {
568 writeEndTag( HtmlMarkup.H6 );
569 }
570 }
571
572
573
574
575
576
577
578
579
580 @Override
581 public void list()
582 {
583 list( null );
584 }
585
586
587
588
589
590 @Override
591 public void list( SinkEventAttributes attributes )
592 {
593 if ( paragraphFlag )
594 {
595
596
597
598 paragraph_();
599 }
600
601 MutableAttributeSet atts = SinkUtils.filterAttributes(
602 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
603
604 writeStartTag( HtmlMarkup.UL, atts );
605 }
606
607
608
609
610
611 @Override
612 public void list_()
613 {
614 writeEndTag( HtmlMarkup.UL );
615 }
616
617
618
619
620
621 @Override
622 public void listItem()
623 {
624 listItem( null );
625 }
626
627
628
629
630
631 @Override
632 public void listItem( SinkEventAttributes attributes )
633 {
634 MutableAttributeSet atts = SinkUtils.filterAttributes(
635 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
636
637 writeStartTag( HtmlMarkup.LI, atts );
638 }
639
640
641
642
643
644 @Override
645 public void listItem_()
646 {
647 writeEndTag( HtmlMarkup.LI );
648 }
649
650
651
652
653
654
655
656 @Override
657 public void numberedList( int numbering )
658 {
659 numberedList( numbering, null );
660 }
661
662
663
664
665
666
667
668 @Override
669 public void numberedList( int numbering, SinkEventAttributes attributes )
670 {
671 if ( paragraphFlag )
672 {
673
674
675
676 paragraph_();
677 }
678
679 String style;
680 switch ( numbering )
681 {
682 case NUMBERING_UPPER_ALPHA:
683 style = "upper-alpha";
684 break;
685 case NUMBERING_LOWER_ALPHA:
686 style = "lower-alpha";
687 break;
688 case NUMBERING_UPPER_ROMAN:
689 style = "upper-roman";
690 break;
691 case NUMBERING_LOWER_ROMAN:
692 style = "lower-roman";
693 break;
694 case NUMBERING_DECIMAL:
695 default:
696 style = "decimal";
697 }
698
699 MutableAttributeSet atts = SinkUtils.filterAttributes(
700 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
701
702 if ( atts == null )
703 {
704 atts = new SinkEventAttributeSet( 1 );
705 }
706
707 atts.addAttribute( Attribute.STYLE, "list-style-type: " + style );
708
709 writeStartTag( HtmlMarkup.OL, atts );
710 }
711
712
713
714
715
716 @Override
717 public void numberedList_()
718 {
719 writeEndTag( HtmlMarkup.OL );
720 }
721
722
723
724
725
726 @Override
727 public void numberedListItem()
728 {
729 numberedListItem( null );
730 }
731
732
733
734
735
736 @Override
737 public void numberedListItem( SinkEventAttributes attributes )
738 {
739 MutableAttributeSet atts = SinkUtils.filterAttributes(
740 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
741
742 writeStartTag( HtmlMarkup.LI, atts );
743 }
744
745
746
747
748
749 @Override
750 public void numberedListItem_()
751 {
752 writeEndTag( HtmlMarkup.LI );
753 }
754
755
756
757
758
759 @Override
760 public void definitionList()
761 {
762 definitionList( null );
763 }
764
765
766
767
768
769 @Override
770 public void definitionList( SinkEventAttributes attributes )
771 {
772 if ( paragraphFlag )
773 {
774
775
776
777 paragraph_();
778 }
779
780 MutableAttributeSet atts = SinkUtils.filterAttributes(
781 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
782
783 writeStartTag( HtmlMarkup.DL, atts );
784 }
785
786
787
788
789
790 @Override
791 public void definitionList_()
792 {
793 writeEndTag( HtmlMarkup.DL );
794 }
795
796
797
798
799
800 @Override
801 public void definedTerm( SinkEventAttributes attributes )
802 {
803 MutableAttributeSet atts = SinkUtils.filterAttributes(
804 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
805
806 writeStartTag( HtmlMarkup.DT, atts );
807 }
808
809
810
811
812
813 @Override
814 public void definedTerm()
815 {
816 definedTerm( null );
817 }
818
819
820
821
822
823 @Override
824 public void definedTerm_()
825 {
826 writeEndTag( HtmlMarkup.DT );
827 }
828
829
830
831
832
833 @Override
834 public void definition()
835 {
836 definition( null );
837 }
838
839
840
841
842
843 @Override
844 public void definition( SinkEventAttributes attributes )
845 {
846 MutableAttributeSet atts = SinkUtils.filterAttributes(
847 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
848
849 writeStartTag( HtmlMarkup.DD, atts );
850 }
851
852
853
854
855
856 @Override
857 public void definition_()
858 {
859 writeEndTag( HtmlMarkup.DD );
860 }
861
862
863
864
865
866
867
868
869 @Override
870 public void figure()
871 {
872 write( String.valueOf( LESS_THAN ) + HtmlMarkup.IMG );
873 legacyFigure = true;
874 }
875
876
877
878
879
880 @Override
881 public void figure( SinkEventAttributes attributes )
882 {
883 inFigure = true;
884
885 MutableAttributeSet atts = SinkUtils.filterAttributes(
886 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
887
888 if ( atts == null )
889 {
890 atts = new SinkEventAttributeSet( 1 );
891 }
892
893 if ( !atts.isDefined( SinkEventAttributes.CLASS ) )
894 {
895 atts.addAttribute( SinkEventAttributes.CLASS, "figure" );
896 }
897
898 writeStartTag( HtmlMarkup.DIV, atts );
899 }
900
901
902 @Override
903 public void figure_()
904 {
905 if ( legacyFigure )
906 {
907 if ( !figureCaptionFlag )
908 {
909
910 write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE + QUOTE );
911 }
912 write( String.valueOf( SPACE ) + SLASH + GREATER_THAN );
913 legacyFigure = false;
914 }
915 else
916 {
917 writeEndTag( HtmlMarkup.DIV );
918 inFigure = false;
919 }
920
921 figureCaptionFlag = false;
922 }
923
924
925
926
927
928
929
930 @Override
931 public void figureGraphics( String name )
932 {
933 write( String.valueOf( SPACE ) + Attribute.SRC + EQUAL + QUOTE + escapeHTML( name ) + QUOTE );
934 }
935
936
937 @Override
938 public void figureGraphics( String src, SinkEventAttributes attributes )
939 {
940 if ( inFigure )
941 {
942 MutableAttributeSet atts = new SinkEventAttributeSet( 1 );
943 atts.addAttribute( SinkEventAttributes.ALIGN, "center" );
944
945 writeStartTag( HtmlMarkup.P, atts );
946 }
947
948 MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, SinkUtils.SINK_IMG_ATTRIBUTES );
949 if ( filtered != null )
950 {
951 filtered.removeAttribute( Attribute.SRC.toString() );
952 }
953
954 int count = ( attributes == null ? 1 : attributes.getAttributeCount() + 1 );
955
956 MutableAttributeSet atts = new SinkEventAttributeSet( count );
957
958 atts.addAttribute( Attribute.SRC, HtmlTools.escapeHTML( src, true ) );
959 atts.addAttributes( filtered );
960
961 if ( atts.getAttribute( Attribute.ALT.toString() ) == null )
962 {
963 atts.addAttribute( Attribute.ALT.toString(), "" );
964 }
965
966 writeStartTag( HtmlMarkup.IMG, atts, true );
967
968 if ( inFigure )
969 {
970 writeEndTag( HtmlMarkup.P );
971 }
972 }
973
974
975
976
977
978
979
980 @Override
981 public void figureCaption()
982 {
983 figureCaptionFlag = true;
984 write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE );
985 legacyFigureCaption = true;
986 }
987
988
989 @Override
990 public void figureCaption( SinkEventAttributes attributes )
991 {
992 if ( legacyFigureCaption )
993 {
994 write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE );
995 legacyFigureCaption = false;
996 figureCaptionFlag = true;
997 }
998 else
999 {
1000 SinkEventAttributeSet atts = new SinkEventAttributeSet( 1 );
1001 atts.addAttribute( SinkEventAttributes.ALIGN, "center" );
1002 atts.addAttributes( SinkUtils.filterAttributes(
1003 attributes, SinkUtils.SINK_BASE_ATTRIBUTES ) );
1004
1005 paragraph( atts );
1006 inline( SinkEventAttributeSet.Semantics.ITALIC );
1007 }
1008 }
1009
1010
1011 @Override
1012 public void figureCaption_()
1013 {
1014 if ( legacyFigureCaption )
1015 {
1016 write( String.valueOf( QUOTE ) );
1017 }
1018 else
1019 {
1020 inline_();
1021 paragraph_();
1022 }
1023 }
1024
1025
1026
1027
1028
1029 @Override
1030 public void paragraph()
1031 {
1032 paragraph( null );
1033 }
1034
1035
1036
1037
1038
1039 @Override
1040 public void paragraph( SinkEventAttributes attributes )
1041 {
1042 paragraphFlag = true;
1043
1044 MutableAttributeSet atts = SinkUtils.filterAttributes(
1045 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
1046
1047 writeStartTag( HtmlMarkup.P, atts );
1048 }
1049
1050
1051
1052
1053
1054 @Override
1055 public void paragraph_()
1056 {
1057 if ( paragraphFlag )
1058 {
1059 writeEndTag( HtmlMarkup.P );
1060 paragraphFlag = false;
1061 }
1062 }
1063
1064
1065
1066
1067
1068 @Override
1069 public void address()
1070 {
1071 address( null );
1072 }
1073
1074
1075
1076
1077
1078 @Override
1079 public void address( SinkEventAttributes attributes )
1080 {
1081 MutableAttributeSet atts = SinkUtils.filterAttributes(
1082 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
1083
1084 writeStartTag( HtmlMarkup.ADDRESS, atts );
1085 }
1086
1087
1088
1089
1090
1091 @Override
1092 public void address_()
1093 {
1094 writeEndTag( HtmlMarkup.ADDRESS );
1095 }
1096
1097
1098
1099
1100
1101 @Override
1102 public void blockquote()
1103 {
1104 blockquote( null );
1105 }
1106
1107
1108
1109
1110
1111 @Override
1112 public void blockquote( SinkEventAttributes attributes )
1113 {
1114 MutableAttributeSet atts = SinkUtils.filterAttributes(
1115 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
1116
1117 writeStartTag( HtmlMarkup.BLOCKQUOTE, atts );
1118 }
1119
1120
1121
1122
1123
1124 @Override
1125 public void blockquote_()
1126 {
1127 writeEndTag( HtmlMarkup.BLOCKQUOTE );
1128 }
1129
1130
1131
1132
1133
1134 @Override
1135 public void division()
1136 {
1137 division( null );
1138 }
1139
1140
1141
1142
1143
1144 @Override
1145 public void division( SinkEventAttributes attributes )
1146 {
1147 MutableAttributeSet atts = SinkUtils.filterAttributes(
1148 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
1149
1150 writeStartTag( HtmlMarkup.DIV, atts );
1151 }
1152
1153
1154
1155
1156
1157 @Override
1158 public void division_()
1159 {
1160 writeEndTag( HtmlMarkup.DIV );
1161 }
1162
1163
1164
1165
1166
1167
1168
1169
1170 @Override
1171 public void verbatim( SinkEventAttributes attributes )
1172 {
1173 if ( paragraphFlag )
1174 {
1175
1176
1177
1178 paragraph_();
1179 }
1180
1181 verbatimFlag = true;
1182
1183 MutableAttributeSet atts = SinkUtils.filterAttributes(
1184 attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES );
1185
1186 if ( atts == null )
1187 {
1188 atts = new SinkEventAttributeSet();
1189 }
1190
1191 boolean boxed = false;
1192
1193 if ( atts.isDefined( SinkEventAttributes.DECORATION ) )
1194 {
1195 boxed =
1196 "boxed".equals( atts.getAttribute( SinkEventAttributes.DECORATION ).toString() );
1197 }
1198
1199 SinkEventAttributes divAtts = null;
1200
1201 if ( boxed )
1202 {
1203 divAtts = new SinkEventAttributeSet( Attribute.CLASS.toString(), "source" );
1204 }
1205
1206 atts.removeAttribute( SinkEventAttributes.DECORATION );
1207
1208 writeStartTag( HtmlMarkup.DIV, divAtts );
1209 writeStartTag( HtmlMarkup.PRE, atts );
1210 }
1211
1212
1213
1214
1215
1216
1217 @Override
1218 public void verbatim_()
1219 {
1220 writeEndTag( HtmlMarkup.PRE );
1221 writeEndTag( HtmlMarkup.DIV );
1222
1223 verbatimFlag = false;
1224
1225 }
1226
1227
1228
1229
1230
1231 @Override
1232 public void horizontalRule()
1233 {
1234 horizontalRule( null );
1235 }
1236
1237
1238
1239
1240
1241 @Override
1242 public void horizontalRule( SinkEventAttributes attributes )
1243 {
1244 MutableAttributeSet atts = SinkUtils.filterAttributes(
1245 attributes, SinkUtils.SINK_HR_ATTRIBUTES );
1246
1247 writeSimpleTag( HtmlMarkup.HR, atts );
1248 }
1249
1250
1251 @Override
1252 public void table()
1253 {
1254
1255 table( null );
1256 }
1257
1258
1259 @Override
1260 public void table( SinkEventAttributes attributes )
1261 {
1262 this.tableContentWriterStack.addLast( new StringWriter() );
1263 this.tableRows = false;
1264
1265 if ( paragraphFlag )
1266 {
1267
1268
1269
1270 paragraph_();
1271 }
1272
1273
1274 if ( attributes == null )
1275 {
1276 this.tableAttributes = new SinkEventAttributeSet( 0 );
1277 }
1278 else
1279 {
1280 this.tableAttributes = SinkUtils.filterAttributes(
1281 attributes, SinkUtils.SINK_TABLE_ATTRIBUTES );
1282 }
1283 }
1284
1285
1286
1287
1288
1289 @Override
1290 public void table_()
1291 {
1292 this.tableRows = false;
1293
1294 writeEndTag( HtmlMarkup.TABLE );
1295
1296 if ( !this.cellCountStack.isEmpty() )
1297 {
1298 this.cellCountStack.removeLast().toString();
1299 }
1300
1301 if ( this.tableContentWriterStack.isEmpty() )
1302 {
1303 LOGGER.warn( "No table content" );
1304 return;
1305 }
1306
1307 String tableContent = this.tableContentWriterStack.removeLast().toString();
1308
1309 String tableCaption = null;
1310 if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
1311 {
1312 tableCaption = this.tableCaptionStack.removeLast();
1313 }
1314
1315 if ( tableCaption != null )
1316 {
1317
1318 StringBuilder sb = new StringBuilder();
1319 sb.append( tableContent, 0, tableContent.indexOf( Markup.GREATER_THAN ) + 1 );
1320 sb.append( tableCaption );
1321 sb.append( tableContent.substring( tableContent.indexOf( Markup.GREATER_THAN ) + 1 ) );
1322
1323 write( sb.toString() );
1324 }
1325 else
1326 {
1327 write( tableContent );
1328 }
1329 }
1330
1331
1332
1333
1334
1335
1336
1337
1338 @Override
1339 public void tableRows( int[] justification, boolean grid )
1340 {
1341 this.tableRows = true;
1342
1343 setCellJustif( justification );
1344
1345 if ( this.tableAttributes == null )
1346 {
1347 this.tableAttributes = new SinkEventAttributeSet( 0 );
1348 }
1349
1350 MutableAttributeSet att = new SinkEventAttributeSet();
1351 if ( !this.tableAttributes.isDefined( Attribute.BORDER.toString() ) )
1352 {
1353 att.addAttribute( Attribute.BORDER, ( grid ? "1" : "0" ) );
1354 }
1355
1356 if ( !this.tableAttributes.isDefined( Attribute.CLASS.toString() ) )
1357 {
1358 att.addAttribute( Attribute.CLASS, "bodyTable" );
1359 }
1360
1361 att.addAttributes( this.tableAttributes );
1362 this.tableAttributes.removeAttributes( this.tableAttributes );
1363
1364 writeStartTag( HtmlMarkup.TABLE, att );
1365
1366 this.cellCountStack.addLast( 0 );
1367 }
1368
1369
1370 @Override
1371 public void tableRows_()
1372 {
1373 this.tableRows = false;
1374 if ( !this.cellJustifStack.isEmpty() )
1375 {
1376 this.cellJustifStack.removeLast();
1377 }
1378 if ( !this.isCellJustifStack.isEmpty() )
1379 {
1380 this.isCellJustifStack.removeLast();
1381 }
1382
1383 this.evenTableRow = true;
1384 }
1385
1386
1387
1388
1389
1390
1391
1392 @Override
1393 public void tableRow()
1394 {
1395
1396 if ( !this.tableRows )
1397 {
1398 tableRows( null, false );
1399 }
1400 tableRow( null );
1401 }
1402
1403
1404
1405
1406
1407
1408
1409 @Override
1410 public void tableRow( SinkEventAttributes attributes )
1411 {
1412 MutableAttributeSet att = new SinkEventAttributeSet();
1413
1414 if ( evenTableRow )
1415 {
1416 att.addAttribute( Attribute.CLASS, "a" );
1417 }
1418 else
1419 {
1420 att.addAttribute( Attribute.CLASS, "b" );
1421 }
1422
1423 att.addAttributes( SinkUtils.filterAttributes(
1424 attributes, SinkUtils.SINK_TR_ATTRIBUTES ) );
1425
1426 writeStartTag( HtmlMarkup.TR, att );
1427
1428 evenTableRow = !evenTableRow;
1429
1430 if ( !this.cellCountStack.isEmpty() )
1431 {
1432 this.cellCountStack.removeLast();
1433 this.cellCountStack.addLast( 0 );
1434 }
1435 }
1436
1437
1438
1439
1440
1441 @Override
1442 public void tableRow_()
1443 {
1444 writeEndTag( HtmlMarkup.TR );
1445 }
1446
1447
1448 @Override
1449 public void tableCell()
1450 {
1451 tableCell( (SinkEventAttributeSet) null );
1452 }
1453
1454
1455 @Override
1456 public void tableHeaderCell()
1457 {
1458 tableHeaderCell( (SinkEventAttributeSet) null );
1459 }
1460
1461
1462 @Override
1463 public void tableCell( SinkEventAttributes attributes )
1464 {
1465 tableCell( false, attributes );
1466 }
1467
1468
1469 @Override
1470 public void tableHeaderCell( SinkEventAttributes attributes )
1471 {
1472 tableCell( true, attributes );
1473 }
1474
1475
1476
1477
1478
1479
1480
1481 private void tableCell( boolean headerRow, MutableAttributeSet attributes )
1482 {
1483 Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
1484
1485 if ( !headerRow && cellCountStack != null && !cellCountStack.isEmpty()
1486 && cellJustifStack != null && !cellJustifStack.isEmpty() && getCellJustif() != null )
1487 {
1488 int cellCount = getCellCount();
1489 if ( cellCount < getCellJustif().length )
1490 {
1491 Map<Integer, MutableAttributeSet> hash = new HashMap<>();
1492 hash.put( Sink.JUSTIFY_CENTER, SinkEventAttributeSet.CENTER );
1493 hash.put( Sink.JUSTIFY_LEFT, SinkEventAttributeSet.LEFT );
1494 hash.put( Sink.JUSTIFY_RIGHT, SinkEventAttributeSet.RIGHT );
1495 MutableAttributeSet atts = hash.get( getCellJustif()[cellCount] );
1496
1497 if ( attributes == null )
1498 {
1499 attributes = new SinkEventAttributeSet();
1500 }
1501 if ( atts != null )
1502 {
1503 attributes.addAttributes( atts );
1504 }
1505 }
1506 }
1507
1508 if ( attributes == null )
1509 {
1510 writeStartTag( t, null );
1511 }
1512 else
1513 {
1514 writeStartTag( t,
1515 SinkUtils.filterAttributes( attributes, SinkUtils.SINK_TD_ATTRIBUTES ) );
1516 }
1517 }
1518
1519
1520 @Override
1521 public void tableCell_()
1522 {
1523 tableCell_( false );
1524 }
1525
1526
1527 @Override
1528 public void tableHeaderCell_()
1529 {
1530 tableCell_( true );
1531 }
1532
1533
1534
1535
1536
1537
1538
1539
1540 private void tableCell_( boolean headerRow )
1541 {
1542 Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
1543
1544 writeEndTag( t );
1545
1546 if ( !this.isCellJustifStack.isEmpty() && this.isCellJustifStack.getLast().equals( Boolean.TRUE )
1547 && !this.cellCountStack.isEmpty() )
1548 {
1549 int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
1550 this.cellCountStack.addLast( ++cellCount );
1551 }
1552 }
1553
1554
1555
1556
1557
1558 @Override
1559 public void tableCaption()
1560 {
1561 tableCaption( null );
1562 }
1563
1564
1565
1566
1567
1568 @Override
1569 public void tableCaption( SinkEventAttributes attributes )
1570 {
1571 StringWriter sw = new StringWriter();
1572 this.tableCaptionWriterStack.addLast( sw );
1573 this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
1574
1575
1576 MutableAttributeSet atts = SinkUtils.filterAttributes(
1577 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
1578
1579 writeStartTag( HtmlMarkup.CAPTION, atts );
1580 }
1581
1582
1583
1584
1585
1586 @Override
1587 public void tableCaption_()
1588 {
1589 writeEndTag( HtmlMarkup.CAPTION );
1590
1591 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1592 {
1593 this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
1594 this.tableCaptionXMLWriterStack.removeLast();
1595 }
1596 }
1597
1598
1599
1600
1601
1602 @Override
1603 public void anchor( String name )
1604 {
1605 anchor( name, null );
1606 }
1607
1608
1609
1610
1611
1612 @Override
1613 public void anchor( String name, SinkEventAttributes attributes )
1614 {
1615 Objects.requireNonNull( name, "name cannot be null" );
1616
1617 if ( headFlag )
1618 {
1619 return;
1620 }
1621
1622 MutableAttributeSet atts = SinkUtils.filterAttributes(
1623 attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
1624
1625 String id = name;
1626
1627 if ( !DoxiaUtils.isValidId( id ) )
1628 {
1629 id = DoxiaUtils.encodeId( name, true );
1630
1631 LOGGER.debug( "Modified invalid anchor name '{}' to '{}'", name, id );
1632 }
1633
1634 MutableAttributeSet att = new SinkEventAttributeSet();
1635 att.addAttribute( Attribute.NAME, id );
1636 att.addAttributes( atts );
1637
1638 writeStartTag( HtmlMarkup.A, att );
1639 }
1640
1641
1642
1643
1644
1645 @Override
1646 public void anchor_()
1647 {
1648 if ( !headFlag )
1649 {
1650 writeEndTag( HtmlMarkup.A );
1651 }
1652 }
1653
1654
1655 @Override
1656 public void link( String name )
1657 {
1658 link( name, null );
1659 }
1660
1661
1662 @Override
1663 public void link( String name, SinkEventAttributes attributes )
1664 {
1665 if ( attributes == null )
1666 {
1667 link( name, null, null );
1668 }
1669 else
1670 {
1671 String target = (String) attributes.getAttribute( Attribute.TARGET.toString() );
1672 MutableAttributeSet atts = SinkUtils.filterAttributes(
1673 attributes, SinkUtils.SINK_LINK_ATTRIBUTES );
1674
1675 link( name, target, atts );
1676 }
1677 }
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689 private void link( String href, String target, MutableAttributeSet attributes )
1690 {
1691 Objects.requireNonNull( href, "href cannot be null" );
1692
1693 if ( headFlag )
1694 {
1695 return;
1696 }
1697
1698 MutableAttributeSet att = new SinkEventAttributeSet();
1699
1700 if ( DoxiaUtils.isExternalLink( href ) )
1701 {
1702 att.addAttribute( Attribute.CLASS, "externalLink" );
1703 }
1704
1705 att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( href ) );
1706
1707 if ( target != null )
1708 {
1709 att.addAttribute( Attribute.TARGET, target );
1710 }
1711
1712 if ( attributes != null )
1713 {
1714 attributes.removeAttribute( Attribute.HREF.toString() );
1715 attributes.removeAttribute( Attribute.TARGET.toString() );
1716 att.addAttributes( attributes );
1717 }
1718
1719 writeStartTag( HtmlMarkup.A, att );
1720 }
1721
1722
1723
1724
1725
1726 @Override
1727 public void link_()
1728 {
1729 if ( !headFlag )
1730 {
1731 writeEndTag( HtmlMarkup.A );
1732 }
1733 }
1734
1735
1736 @Override
1737 public void inline()
1738 {
1739 inline( null );
1740 }
1741
1742 private void inlineSemantics( SinkEventAttributes attributes, String semantic,
1743 List<Tag> tags, Tag tag )
1744 {
1745 if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, semantic ) )
1746 {
1747 SinkEventAttributes attributesNoSemantics = ( SinkEventAttributes ) attributes.copyAttributes();
1748 attributesNoSemantics.removeAttribute( SinkEventAttributes.SEMANTICS );
1749 writeStartTag( tag, attributesNoSemantics );
1750 tags.add( 0, tag );
1751 }
1752 }
1753
1754
1755 @Override
1756 public void inline( SinkEventAttributes attributes )
1757 {
1758 if ( !headFlag )
1759 {
1760 List<Tag> tags = new ArrayList<>();
1761
1762 if ( attributes != null )
1763 {
1764 inlineSemantics( attributes, "emphasis", tags, HtmlMarkup.EM );
1765 inlineSemantics( attributes, "strong", tags, HtmlMarkup.STRONG );
1766 inlineSemantics( attributes, "small", tags, HtmlMarkup.SMALL );
1767 inlineSemantics( attributes, "line-through", tags, HtmlMarkup.S );
1768 inlineSemantics( attributes, "citation", tags, HtmlMarkup.CITE );
1769 inlineSemantics( attributes, "quote", tags, HtmlMarkup.Q );
1770 inlineSemantics( attributes, "definition", tags, HtmlMarkup.DFN );
1771 inlineSemantics( attributes, "abbreviation", tags, HtmlMarkup.ABBR );
1772 inlineSemantics( attributes, "italic", tags, HtmlMarkup.I );
1773 inlineSemantics( attributes, "bold", tags, HtmlMarkup.B );
1774 inlineSemantics( attributes, "monospaced", tags, HtmlMarkup.TT );
1775 inlineSemantics( attributes, "code", tags, HtmlMarkup.CODE );
1776 inlineSemantics( attributes, "variable", tags, HtmlMarkup.VAR );
1777 inlineSemantics( attributes, "sample", tags, HtmlMarkup.SAMP );
1778 inlineSemantics( attributes, "keyboard", tags, HtmlMarkup.KBD );
1779 inlineSemantics( attributes, "superscript", tags, HtmlMarkup.SUP );
1780 inlineSemantics( attributes, "subscript", tags, HtmlMarkup.SUB );
1781 inlineSemantics( attributes, "annotation", tags, HtmlMarkup.U );
1782 inlineSemantics( attributes, "bidirectionalOverride", tags, HtmlMarkup.BDO );
1783 inlineSemantics( attributes, "phrase", tags, HtmlMarkup.SPAN );
1784 inlineSemantics( attributes, "insert", tags, HtmlMarkup.INS );
1785 inlineSemantics( attributes, "delete", tags, HtmlMarkup.DEL );
1786 }
1787
1788 inlineStack.push( tags );
1789 }
1790 }
1791
1792
1793 @Override
1794 public void inline_()
1795 {
1796 if ( !headFlag )
1797 {
1798 for ( Tag tag: inlineStack.pop() )
1799 {
1800 writeEndTag( tag );
1801 }
1802 }
1803 }
1804
1805
1806
1807
1808
1809 @Override
1810 public void italic()
1811 {
1812 inline( SinkEventAttributeSet.Semantics.ITALIC );
1813 }
1814
1815
1816
1817
1818
1819 @Override
1820 public void italic_()
1821 {
1822 inline_();
1823 }
1824
1825
1826
1827
1828
1829 @Override
1830 public void bold()
1831 {
1832 inline( SinkEventAttributeSet.Semantics.BOLD );
1833 }
1834
1835
1836
1837
1838
1839 @Override
1840 public void bold_()
1841 {
1842 inline_();
1843 }
1844
1845
1846
1847
1848
1849 @Override
1850 public void monospaced()
1851 {
1852 inline( SinkEventAttributeSet.Semantics.MONOSPACED );
1853 }
1854
1855
1856
1857
1858
1859 @Override
1860 public void monospaced_()
1861 {
1862 inline_();
1863 }
1864
1865
1866
1867
1868
1869 @Override
1870 public void lineBreak()
1871 {
1872 lineBreak( null );
1873 }
1874
1875
1876
1877
1878
1879 @Override
1880 public void lineBreak( SinkEventAttributes attributes )
1881 {
1882 if ( headFlag || isVerbatimFlag() )
1883 {
1884 getTextBuffer().append( EOL );
1885 }
1886 else
1887 {
1888 MutableAttributeSet atts = SinkUtils.filterAttributes(
1889 attributes, SinkUtils.SINK_BR_ATTRIBUTES );
1890
1891 writeSimpleTag( HtmlMarkup.BR, atts );
1892 }
1893 }
1894
1895
1896 @Override
1897 public void pageBreak()
1898 {
1899 comment( " PB " );
1900 }
1901
1902
1903 @Override
1904 public void nonBreakingSpace()
1905 {
1906 if ( headFlag )
1907 {
1908 getTextBuffer().append( ' ' );
1909 }
1910 else
1911 {
1912 write( " " );
1913 }
1914 }
1915
1916
1917 @Override
1918 public void text( String text )
1919 {
1920 if ( headFlag )
1921 {
1922 getTextBuffer().append( text );
1923 }
1924 else if ( verbatimFlag )
1925 {
1926 verbatimContent( text );
1927 }
1928 else
1929 {
1930 content( text );
1931 }
1932 }
1933
1934
1935 @Override
1936 public void text( String text, SinkEventAttributes attributes )
1937 {
1938 text( text );
1939 }
1940
1941
1942 @Override
1943 public void rawText( String text )
1944 {
1945 if ( headFlag )
1946 {
1947 getTextBuffer().append( text );
1948 }
1949 else
1950 {
1951 write( text );
1952 }
1953 }
1954
1955
1956 @Override
1957 public void comment( String comment )
1958 {
1959 if ( comment != null )
1960 {
1961 final String originalComment = comment;
1962
1963
1964 while ( comment.contains( "--" ) )
1965 {
1966 comment = comment.replace( "--", "- -" );
1967 }
1968
1969 if ( comment.endsWith( "-" ) )
1970 {
1971 comment += " ";
1972 }
1973
1974 if ( !originalComment.equals( comment ) )
1975 {
1976 LOGGER.warn( "Modified invalid comment '{}' to '{}'", originalComment, comment );
1977 }
1978
1979 final StringBuilder buffer = new StringBuilder( comment.length() + 7 );
1980
1981 buffer.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS );
1982 buffer.append( comment );
1983 buffer.append( MINUS ).append( MINUS ).append( GREATER_THAN );
1984
1985 write( buffer.toString() );
1986 }
1987 }
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030 @Override
2031 public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
2032 {
2033 if ( requiredParams == null || !( requiredParams[0] instanceof Integer ) )
2034 {
2035 LOGGER.warn( "No type information for unknown event '{}', ignoring!", name );
2036
2037 return;
2038 }
2039
2040 int tagType = (Integer) requiredParams[0];
2041
2042 if ( tagType == ENTITY_TYPE )
2043 {
2044 rawText( name );
2045
2046 return;
2047 }
2048
2049 if ( tagType == CDATA_TYPE )
2050 {
2051 rawText( EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL );
2052
2053 return;
2054 }
2055
2056 Tag tag = HtmlTools.getHtmlTag( name );
2057
2058 if ( tag == null )
2059 {
2060 LOGGER.warn( "No HTML tag found for unknown event '{}', ignoring!", name );
2061 }
2062 else
2063 {
2064 if ( tagType == TAG_TYPE_SIMPLE )
2065 {
2066 writeSimpleTag( tag, escapeAttributeValues( attributes ) );
2067 }
2068 else if ( tagType == TAG_TYPE_START )
2069 {
2070 writeStartTag( tag, escapeAttributeValues( attributes ) );
2071 }
2072 else if ( tagType == TAG_TYPE_END )
2073 {
2074 writeEndTag( tag );
2075 }
2076 else
2077 {
2078 LOGGER.warn( "No type information for unknown event '{}', ignoring!", name );
2079 }
2080 }
2081 }
2082
2083 private SinkEventAttributes escapeAttributeValues( SinkEventAttributes attributes )
2084 {
2085 SinkEventAttributeSet set = new SinkEventAttributeSet( attributes.getAttributeCount() );
2086
2087 Enumeration<?> names = attributes.getAttributeNames();
2088
2089 while ( names.hasMoreElements() )
2090 {
2091 Object name = names.nextElement();
2092
2093 set.addAttribute( name, escapeHTML( attributes.getAttribute( name ).toString() ) );
2094 }
2095
2096 return set;
2097 }
2098
2099
2100 @Override
2101 public void flush()
2102 {
2103 writer.flush();
2104 }
2105
2106
2107 @Override
2108 public void close()
2109 {
2110 writer.close();
2111
2112 init();
2113 }
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124 protected void content( String text )
2125 {
2126
2127 String txt = escapeHTML( text );
2128 txt = StringUtils.replace( txt, "&#", "&#" );
2129 write( txt );
2130 }
2131
2132
2133
2134
2135
2136
2137 protected void verbatimContent( String text )
2138 {
2139 write( escapeHTML( text ) );
2140 }
2141
2142
2143
2144
2145
2146
2147
2148
2149 protected static String escapeHTML( String text )
2150 {
2151 return HtmlTools.escapeHTML( text, false );
2152 }
2153
2154
2155
2156
2157
2158
2159
2160
2161 protected static String encodeURL( String text )
2162 {
2163 return HtmlTools.encodeURL( text );
2164 }
2165
2166
2167 protected void write( String text )
2168 {
2169 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
2170 {
2171 this.tableCaptionXMLWriterStack.getLast().writeMarkup( unifyEOLs( text ) );
2172 }
2173 else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
2174 {
2175 this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
2176 }
2177 else
2178 {
2179 writer.write( unifyEOLs( text ) );
2180 }
2181 }
2182
2183
2184 @Override
2185 protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
2186 {
2187 if ( this.tableCaptionXMLWriterStack.isEmpty() )
2188 {
2189 super.writeStartTag ( t, att, isSimpleTag );
2190 }
2191 else
2192 {
2193 String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
2194 this.tableCaptionXMLWriterStack.getLast().startElement( tag );
2195
2196 if ( att != null )
2197 {
2198 Enumeration<?> names = att.getAttributeNames();
2199 while ( names.hasMoreElements() )
2200 {
2201 Object key = names.nextElement();
2202 Object value = att.getAttribute( key );
2203
2204 this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
2205 }
2206 }
2207
2208 if ( isSimpleTag )
2209 {
2210 this.tableCaptionXMLWriterStack.getLast().endElement();
2211 }
2212 }
2213 }
2214
2215
2216 @Override
2217 protected void writeEndTag( Tag t )
2218 {
2219 if ( this.tableCaptionXMLWriterStack.isEmpty() )
2220 {
2221 super.writeEndTag( t );
2222 }
2223 else
2224 {
2225 this.tableCaptionXMLWriterStack.getLast().endElement();
2226 }
2227 }
2228 }