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