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