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