1 package org.apache.maven.doxia.module.fo;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.io.Writer;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.LinkedList;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.Stack;
33 import java.util.TreeSet;
34
35 import javax.swing.text.MutableAttributeSet;
36 import javax.swing.text.html.HTML.Attribute;
37 import javax.swing.text.html.HTML.Tag;
38
39 import org.apache.maven.doxia.sink.SinkEventAttributes;
40 import org.apache.maven.doxia.sink.impl.AbstractXmlSink;
41 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
42 import org.apache.maven.doxia.sink.impl.SinkUtils;
43 import org.apache.maven.doxia.util.DoxiaUtils;
44 import org.apache.maven.doxia.util.HtmlTools;
45
46 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class FoSink
63 extends AbstractXmlSink
64 implements FoMarkup
65 {
66
67 private final PrintWriter out;
68
69
70 private final Stack<NumberedListItem> listStack;
71
72
73 private final FoConfiguration config;
74
75
76 private int section = 0;
77
78
79 private int subsection = 0;
80
81
82 private int subsubsection = 0;
83
84
85 private boolean verbatim;
86
87
88 private boolean inFigure;
89
90 private final String encoding;
91
92 private final String languageId;
93
94
95 private final LinkedList<Boolean> tableGridStack;
96
97
98 private final LinkedList<int[]> cellJustifStack;
99
100
101 private final LinkedList<Boolean> isCellJustifStack;
102
103
104 private final LinkedList<Integer> cellCountStack;
105
106
107 private final LinkedList<StringWriter> tableContentWriterStack;
108
109 private final LinkedList<StringWriter> tableCaptionWriterStack;
110
111 private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
112
113
114 private final LinkedList<String> tableCaptionStack;
115
116
117
118 protected Map<String, Set<String>> warnMessages;
119
120
121
122
123
124
125
126 protected FoSink( Writer writer )
127 {
128 this( writer, "UTF-8" );
129 }
130
131
132
133
134
135
136
137
138 protected FoSink( Writer writer, String encoding )
139 {
140 this( writer, encoding, null );
141 }
142
143
144
145
146
147
148
149
150
151
152
153 protected FoSink( Writer writer, String encoding, String languageId )
154 {
155 if ( writer == null )
156 {
157 throw new NullPointerException( "Null writer in FO Sink!" );
158 }
159
160 this.out = new PrintWriter( writer );
161 this.encoding = encoding;
162 this.languageId = languageId;
163 this.config = new FoConfiguration();
164
165 this.listStack = new Stack<NumberedListItem>();
166 this.tableGridStack = new LinkedList<Boolean>();
167 this.cellJustifStack = new LinkedList<int[]>();
168 this.isCellJustifStack = new LinkedList<Boolean>();
169 this.cellCountStack = new LinkedList<Integer>();
170 this.tableContentWriterStack = new LinkedList<StringWriter>();
171 this.tableCaptionWriterStack = new LinkedList<StringWriter>();
172 this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>();
173 this.tableCaptionStack = new LinkedList<String>();
174
175 setNameSpace( "fo" );
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190 public void load( File configFile )
191 throws IOException
192 {
193 config.load( configFile );
194 }
195
196
197 public void head( SinkEventAttributes attributes )
198 {
199 init();
200
201 startPageSequence( "0", null, null );
202 }
203
204
205 public void head()
206 {
207 head( null );
208 }
209
210
211 public void head_()
212 {
213 writeEOL();
214 }
215
216
217 public void title( SinkEventAttributes attributes )
218 {
219 writeStartTag( BLOCK_TAG, "doc.header.title" );
220 }
221
222
223 public void title()
224 {
225 title( null );
226 }
227
228
229 public void title_()
230 {
231 writeEndTag( BLOCK_TAG );
232 writeEOL();
233 }
234
235
236 public void author( SinkEventAttributes attributes )
237 {
238 writeStartTag( BLOCK_TAG, "doc.header.author" );
239 }
240
241
242 public void author()
243 {
244 author( null );
245 }
246
247
248 public void author_()
249 {
250 writeEndTag( BLOCK_TAG );
251 writeEOL();
252 }
253
254
255 public void date( SinkEventAttributes attributes )
256 {
257 writeStartTag( BLOCK_TAG, "doc.header.date" );
258 }
259
260
261 public void date()
262 {
263 date( null );
264 }
265
266
267 public void date_()
268 {
269 writeEndTag( BLOCK_TAG );
270 writeEOL();
271 }
272
273
274 public void body( SinkEventAttributes attributes )
275 {
276
277 }
278
279
280 public void body()
281 {
282 body( null );
283 }
284
285
286 public void body_()
287 {
288 writeEOL();
289 writeEndTag( FLOW_TAG );
290 writeEOL();
291 writeEndTag( PAGE_SEQUENCE_TAG );
292 writeEOL();
293 endDocument();
294 }
295
296
297
298
299
300
301 public void sectionTitle()
302 {
303
304 }
305
306
307 public void sectionTitle_()
308 {
309
310 }
311
312
313 public void section( int level, SinkEventAttributes attributes )
314 {
315 if ( level == SECTION_LEVEL_1 )
316 {
317 section++;
318 subsection = 0;
319 subsubsection = 0;
320 }
321 else if ( level == SECTION_LEVEL_2 )
322 {
323 subsection++;
324 subsubsection = 0;
325 }
326 else if ( level == SECTION_LEVEL_3 )
327 {
328 subsubsection++;
329 }
330
331 onSection();
332 }
333
334
335 public void section_( int level )
336 {
337 onSection_();
338 }
339
340
341 public void sectionTitle( int level, SinkEventAttributes attributes )
342 {
343 onSectionTitle( level );
344 }
345
346
347 public void sectionTitle_( int level )
348 {
349 onSectionTitle_();
350 }
351
352
353 public void section1()
354 {
355 section( SECTION_LEVEL_1, null );
356 }
357
358
359 public void sectionTitle1()
360 {
361 sectionTitle( SECTION_LEVEL_1, null );
362 }
363
364
365 public void sectionTitle1_()
366 {
367 sectionTitle_( SECTION_LEVEL_1 );
368 }
369
370
371 public void section1_()
372 {
373 section_( SECTION_LEVEL_1 );
374 }
375
376
377 public void section2()
378 {
379 section( SECTION_LEVEL_2, null );
380 }
381
382
383 public void sectionTitle2()
384 {
385 sectionTitle( SECTION_LEVEL_2, null );
386 }
387
388
389 public void sectionTitle2_()
390 {
391 sectionTitle_( SECTION_LEVEL_2 );
392 }
393
394
395 public void section2_()
396 {
397 section_( SECTION_LEVEL_2 );
398 }
399
400
401 public void section3()
402 {
403 section( SECTION_LEVEL_3, null );
404 }
405
406
407 public void sectionTitle3()
408 {
409 sectionTitle( SECTION_LEVEL_3, null );
410 }
411
412
413 public void sectionTitle3_()
414 {
415 sectionTitle_( SECTION_LEVEL_3 );
416 }
417
418
419 public void section3_()
420 {
421 section_( SECTION_LEVEL_3 );
422 }
423
424
425 public void section4()
426 {
427 section( SECTION_LEVEL_4, null );
428 }
429
430
431 public void sectionTitle4()
432 {
433 sectionTitle( SECTION_LEVEL_4, null );
434 }
435
436
437 public void sectionTitle4_()
438 {
439 sectionTitle_( SECTION_LEVEL_4 );
440 }
441
442
443 public void section4_()
444 {
445 section_( SECTION_LEVEL_4 );
446 }
447
448
449 public void section5()
450 {
451 section( SECTION_LEVEL_5, null );
452 }
453
454
455 public void sectionTitle5()
456 {
457 sectionTitle( SECTION_LEVEL_5, null );
458 }
459
460
461 public void sectionTitle5_()
462 {
463 sectionTitle_( SECTION_LEVEL_5 );
464 }
465
466
467 public void section5_()
468 {
469 section_( SECTION_LEVEL_5 );
470 }
471
472
473 private void onSection()
474 {
475 writeEOL();
476 writeStartTag( BLOCK_TAG, "body.text" );
477 }
478
479
480
481
482
483
484 private void onSectionTitle( int depth )
485 {
486 StringBuilder title = new StringBuilder( 16 );
487
488 title.append( getChapterString() );
489
490 writeEOL();
491 if ( depth == SECTION_LEVEL_1 )
492 {
493 writeStartTag( BLOCK_TAG, "body.h1" );
494 title.append( section ).append( " " );
495 }
496 else if ( depth == SECTION_LEVEL_2 )
497 {
498 writeStartTag( BLOCK_TAG, "body.h2" );
499 title.append( section ).append( "." );
500 title.append( subsection ).append( " " );
501 }
502 else if ( depth == SECTION_LEVEL_3 )
503 {
504 writeStartTag( BLOCK_TAG, "body.h3" );
505 title.append( section ).append( "." );
506 title.append( subsection ).append( "." );
507 title.append( subsubsection ).append( " " );
508 }
509 else if ( depth == SECTION_LEVEL_4 )
510 {
511 writeStartTag( BLOCK_TAG, "body.h4" );
512 }
513 else
514 {
515 writeStartTag( BLOCK_TAG, "body.h5" );
516 }
517
518 write( title.toString() );
519 }
520
521
522 private void onSectionTitle_()
523 {
524 writeEndTag( BLOCK_TAG );
525 writeEOL();
526 }
527
528
529 private void onSection_()
530 {
531 writeEndTag( BLOCK_TAG );
532 writeEOL();
533 }
534
535
536
537
538
539 protected void resetSectionCounter()
540 {
541 this.section = 0;
542 }
543
544
545
546
547
548
549
550 protected String getChapterString()
551 {
552 return "";
553 }
554
555
556
557
558
559
560 public void list( SinkEventAttributes attributes )
561 {
562 writeEOL();
563 writeStartTag( LIST_BLOCK_TAG, "list" );
564 }
565
566
567 public void list()
568 {
569 list( null );
570 }
571
572
573 public void list_()
574 {
575 writeEndTag( LIST_BLOCK_TAG );
576 writeEOL();
577 }
578
579
580 public void listItem( SinkEventAttributes attributes )
581 {
582 writeStartTag( LIST_ITEM_TAG, "list.item" );
583 writeStartTag( LIST_ITEM_LABEL_TAG );
584 writeStartTag( BLOCK_TAG );
585 write( "•" );
586 writeEndTag( BLOCK_TAG );
587 writeEndTag( LIST_ITEM_LABEL_TAG );
588 writeEOL();
589 writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
590 writeEOL();
591 writeStartTag( BLOCK_TAG );
592 }
593
594
595 public void listItem()
596 {
597 listItem( null );
598 }
599
600
601 public void listItem_()
602 {
603 writeEndTag( BLOCK_TAG );
604 writeEOL();
605 writeEndTag( LIST_ITEM_BODY_TAG );
606 writeEOL();
607 writeEndTag( LIST_ITEM_TAG );
608 writeEOL();
609 }
610
611
612 public void numberedList( int numbering, SinkEventAttributes attributes )
613 {
614 this.listStack.push( new NumberedListItem( numbering ) );
615 writeEOL();
616 writeStartTag( LIST_BLOCK_TAG, "list" );
617 }
618
619
620 public void numberedList( int numbering )
621 {
622 numberedList( numbering, null );
623 }
624
625
626 public void numberedList_()
627 {
628 this.listStack.pop();
629 writeEndTag( LIST_BLOCK_TAG );
630 writeEOL();
631 }
632
633
634 public void numberedListItem( SinkEventAttributes attributes )
635 {
636 NumberedListItem current = this.listStack.peek();
637 current.next();
638
639 writeStartTag( LIST_ITEM_TAG, "list.item" );
640
641 writeEOL();
642 writeStartTag( LIST_ITEM_LABEL_TAG );
643 writeEOL();
644 writeStartTag( BLOCK_TAG );
645 write( current.getListItemSymbol() );
646 writeEndTag( BLOCK_TAG );
647 writeEOL();
648 writeEndTag( LIST_ITEM_LABEL_TAG );
649 writeEOL();
650
651 writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
652 writeEOL();
653 writeStartTag( BLOCK_TAG );
654 }
655
656
657 public void numberedListItem()
658 {
659 numberedListItem( null );
660 }
661
662
663 public void numberedListItem_()
664 {
665 writeEndTag( BLOCK_TAG );
666 writeEOL();
667 writeEndTag( LIST_ITEM_BODY_TAG );
668 writeEOL();
669 writeEndTag( LIST_ITEM_TAG );
670 writeEOL();
671 }
672
673
674 public void definitionList( SinkEventAttributes attributes )
675 {
676 writeEOL();
677 writeStartTag( BLOCK_TAG, "dl" );
678 }
679
680
681 public void definitionList()
682 {
683 definitionList( null );
684 }
685
686
687 public void definitionList_()
688 {
689 writeEndTag( BLOCK_TAG );
690 writeEOL();
691 }
692
693
694 public void definitionListItem( SinkEventAttributes attributes )
695 {
696
697 }
698
699
700 public void definitionListItem()
701 {
702 definitionListItem( null );
703 }
704
705
706 public void definitionListItem_()
707 {
708
709 }
710
711
712 public void definedTerm( SinkEventAttributes attributes )
713 {
714 writeStartTag( BLOCK_TAG, "dt" );
715 }
716
717
718 public void definedTerm()
719 {
720 definedTerm( null );
721 }
722
723
724 public void definedTerm_()
725 {
726 writeEndTag( BLOCK_TAG );
727 writeEOL();
728 }
729
730
731 public void definition( SinkEventAttributes attributes )
732 {
733 writeEOL();
734 writeStartTag( BLOCK_TAG, "dd" );
735 }
736
737
738 public void definition()
739 {
740 definition( null );
741 }
742
743
744 public void definition_()
745 {
746 writeEndTag( BLOCK_TAG );
747 writeEOL();
748 }
749
750
751 public void figure( SinkEventAttributes attributes )
752 {
753 this.inFigure = true;
754 writeEOL();
755 writeStartTag( BLOCK_TAG, "figure.display" );
756 }
757
758
759 public void figure()
760 {
761 figure( null );
762 }
763
764
765 public void figure_()
766 {
767 this.inFigure = false;
768 writeEndTag( BLOCK_TAG );
769 writeEOL();
770 }
771
772
773 public void figureGraphics( String name )
774 {
775 figureGraphics( name, null );
776 }
777
778
779 public void figureGraphics( String src, SinkEventAttributes attributes )
780 {
781 MutableAttributeSet atts = config.getAttributeSet( "figure.graphics" );
782 atts.addAttribute( Attribute.SRC.toString(), src );
783
784
785
786 final String[] valids = new String[] {"content-height", "content-width", "height", "width"};
787 final MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, valids );
788
789 if ( filtered != null )
790 {
791 atts.addAttributes( filtered );
792 }
793
794 writeln( "<fo:external-graphic" + SinkUtils.getAttributeString( atts ) + "/>" );
795 }
796
797
798
799
800
801
802 protected boolean isFigure()
803 {
804 return this.inFigure;
805 }
806
807
808 public void figureCaption( SinkEventAttributes attributes )
809 {
810 writeStartTag( BLOCK_TAG, "figure.caption" );
811 }
812
813
814 public void figureCaption()
815 {
816 figureCaption( null );
817 }
818
819
820 public void figureCaption_()
821 {
822 writeEndTag( BLOCK_TAG );
823 writeEOL();
824 }
825
826
827 public void paragraph()
828 {
829 paragraph( null );
830 }
831
832
833 public void paragraph( SinkEventAttributes attributes )
834 {
835 MutableAttributeSet atts = config.getAttributeSet( "normal.paragraph" );
836
837 if ( attributes != null && attributes.isDefined( SinkEventAttributes.ALIGN ) )
838 {
839 atts.addAttribute( "text-align", attributes.getAttribute( SinkEventAttributes.ALIGN ) );
840 }
841
842 writeEOL();
843 writeStartTag( BLOCK_TAG, atts );
844 }
845
846
847 public void paragraph_()
848 {
849 writeEndTag( BLOCK_TAG );
850 writeEOL();
851 }
852
853
854 public void verbatim( SinkEventAttributes attributes )
855 {
856 this.verbatim = true;
857
858 boolean boxed = false;
859
860 if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) )
861 {
862 boxed =
863 "boxed".equals( attributes.getAttribute( SinkEventAttributes.DECORATION ).toString() );
864 }
865
866 if ( boxed )
867 {
868 writeStartTag( BLOCK_TAG, "body.source" );
869 }
870 else
871 {
872 writeStartTag( BLOCK_TAG, "body.pre" );
873 }
874 }
875
876
877 public void verbatim( boolean boxed )
878 {
879 verbatim( boxed ? SinkEventAttributeSet.BOXED : null );
880 }
881
882
883 public void verbatim_()
884 {
885 this.verbatim = false;
886 writeEndTag( BLOCK_TAG );
887 writeEOL();
888 }
889
890
891 public void horizontalRule( SinkEventAttributes attributes )
892 {
893 writeEOL();
894 writeEOL();
895 writeStartTag( BLOCK_TAG );
896 writeEmptyTag( LEADER_TAG, "body.rule" );
897 writeEndTag( BLOCK_TAG );
898 writeEOL();
899 }
900
901
902 public void horizontalRule()
903 {
904 horizontalRule( null );
905 }
906
907
908 public void pageBreak()
909 {
910 writeEmptyTag( BLOCK_TAG, "break-before", "page" );
911 writeEOL();
912 }
913
914
915 public void table( SinkEventAttributes attributes )
916 {
917 writeEOL();
918 writeStartTag( BLOCK_TAG, "table.padding" );
919
920
921
922
923 this.tableContentWriterStack.addLast( new StringWriter() );
924 writeStartTag( TABLE_TAG, "table.layout" );
925 }
926
927
928 public void table()
929 {
930 table( null );
931 }
932
933
934 public void table_()
935 {
936 String content = this.tableContentWriterStack.removeLast().toString();
937
938 StringBuilder sb = new StringBuilder();
939 int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
940 for ( int i = 0; i < cellCount; i++ )
941 {
942 sb.append( "<fo:table-column column-width=\"proportional-column-width(1)\"/>" );
943 sb.append( EOL );
944 }
945
946 int index = content.indexOf( ">" ) + 1;
947 writeln( content.substring( 0, index ) );
948 write( sb.toString() );
949 write( content.substring( index ) );
950
951 writeEndTag( TABLE_TAG );
952 writeEOL();
953
954
955
956
957 writeEndTag( BLOCK_TAG );
958 writeEOL();
959
960 if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
961 {
962 paragraph( SinkEventAttributeSet.CENTER );
963 write( this.tableCaptionStack.removeLast().toString() );
964 paragraph_();
965 }
966 }
967
968
969 public void tableRows( int[] justification, boolean grid )
970 {
971 this.tableGridStack.addLast( Boolean.valueOf( grid ) );
972 this.cellJustifStack.addLast( justification );
973 this.isCellJustifStack.addLast( Boolean.valueOf( true ) );
974 this.cellCountStack.addLast( Integer.valueOf( 0 ) );
975 writeEOL();
976 writeStartTag( TABLE_BODY_TAG );
977 }
978
979
980 public void tableRows_()
981 {
982 this.tableGridStack.removeLast();
983 this.cellJustifStack.removeLast();
984 this.isCellJustifStack.removeLast();
985 writeEndTag( TABLE_BODY_TAG );
986 writeEOL();
987 }
988
989
990 public void tableRow( SinkEventAttributes attributes )
991 {
992
993 writeStartTag( TABLE_ROW_TAG, "table.body.row" );
994 this.cellCountStack.removeLast();
995 this.cellCountStack.addLast( Integer.valueOf( 0 ) );
996 }
997
998
999 public void tableRow()
1000 {
1001 tableRow( null );
1002 }
1003
1004
1005 public void tableRow_()
1006 {
1007 writeEndTag( TABLE_ROW_TAG );
1008 writeEOL();
1009 }
1010
1011
1012 public void tableCell( SinkEventAttributes attributes )
1013 {
1014 tableCell( false, attributes );
1015 }
1016
1017
1018 public void tableCell()
1019 {
1020 tableCell( (SinkEventAttributes) null );
1021 }
1022
1023
1024 public void tableCell( String width )
1025 {
1026
1027 tableCell();
1028 }
1029
1030
1031 public void tableHeaderCell( SinkEventAttributes attributes )
1032 {
1033 tableCell( true, attributes );
1034 }
1035
1036
1037 public void tableHeaderCell()
1038 {
1039 tableHeaderCell( (SinkEventAttributes) null );
1040 }
1041
1042
1043 public void tableHeaderCell( String width )
1044 {
1045
1046 tableHeaderCell();
1047 }
1048
1049
1050
1051
1052
1053
1054
1055 private void tableCell( boolean headerRow, SinkEventAttributes attributes )
1056 {
1057 MutableAttributeSet cellAtts = headerRow
1058 ? config.getAttributeSet( "table.heading.cell" )
1059 : config.getAttributeSet( "table.body.cell" );
1060
1061
1062 int cellCount = Integer.parseInt( this.cellCountStack.getLast().toString() );
1063 cellAtts.addAttribute( "column-number", String.valueOf( cellCount + 1 ) );
1064
1065 if ( this.tableGridStack.getLast().equals( Boolean.TRUE ) )
1066 {
1067 cellAtts.addAttributes( config.getAttributeSet( "table.body.cell.grid" ) );
1068 }
1069
1070 MutableAttributeSet blockAtts = headerRow
1071 ? config.getAttributeSet( "table.heading.block" )
1072 : config.getAttributeSet( "table.body.block" );
1073
1074 String justif = null;
1075 if ( attributes == null )
1076 {
1077 attributes = new SinkEventAttributeSet( 0 );
1078 }
1079
1080 if ( attributes.isDefined( Attribute.ALIGN.toString() ) )
1081 {
1082 justif = attributes.getAttribute( Attribute.ALIGN.toString() ).toString();
1083 }
1084
1085 int[] cellJustif = this.cellJustifStack.getLast();
1086 if ( justif == null && cellJustif != null && cellJustif.length > 0
1087 && this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
1088 {
1089 switch ( cellJustif[Math.min( cellCount, cellJustif.length - 1 )] )
1090 {
1091 case JUSTIFY_LEFT:
1092 justif = "left";
1093 break;
1094 case JUSTIFY_RIGHT:
1095 justif = "right";
1096 break;
1097 case JUSTIFY_CENTER:
1098 default:
1099 justif = "center";
1100 }
1101 }
1102
1103 if ( justif != null )
1104 {
1105 blockAtts.addAttribute( "text-align", justif );
1106 }
1107
1108 writeStartTag( TABLE_CELL_TAG, cellAtts );
1109 writeEOL();
1110 writeStartTag( BLOCK_TAG, blockAtts );
1111 writeEOL();
1112 }
1113
1114
1115 public void tableCell_()
1116 {
1117 writeEndTag( BLOCK_TAG );
1118 writeEOL();
1119 writeEndTag( TABLE_CELL_TAG );
1120 writeEOL();
1121
1122 if ( this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
1123 {
1124 int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
1125 this.cellCountStack.addLast( Integer.valueOf( ++cellCount ) );
1126 }
1127 }
1128
1129
1130 public void tableHeaderCell_()
1131 {
1132 tableCell_();
1133 }
1134
1135
1136 public void tableCaption( SinkEventAttributes attributes )
1137 {
1138 StringWriter sw = new StringWriter();
1139 this.tableCaptionWriterStack.addLast( sw );
1140 this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
1141
1142
1143
1144
1145
1146
1147 }
1148
1149
1150 public void tableCaption()
1151 {
1152 tableCaption( null );
1153 }
1154
1155
1156 public void tableCaption_()
1157 {
1158 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1159 {
1160 this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
1161 this.tableCaptionXMLWriterStack.removeLast();
1162 }
1163
1164
1165 }
1166
1167
1168 public void anchor( String name, SinkEventAttributes attributes )
1169 {
1170 if ( name == null )
1171 {
1172 throw new NullPointerException( "Anchor name cannot be null!" );
1173 }
1174
1175 String anchor = name;
1176
1177 if ( !DoxiaUtils.isValidId( anchor ) )
1178 {
1179 anchor = DoxiaUtils.encodeId( name, true );
1180
1181 String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
1182 logMessage( "modifiedLink", msg );
1183 }
1184
1185 anchor = "#" + name;
1186
1187 writeStartTag( INLINE_TAG, "id", anchor );
1188 }
1189
1190
1191 public void anchor( String name )
1192 {
1193 anchor( name, null );
1194 }
1195
1196
1197 public void anchor_()
1198 {
1199 writeEndTag( INLINE_TAG );
1200 }
1201
1202
1203 public void link( String name, SinkEventAttributes attributes )
1204 {
1205 if ( name == null )
1206 {
1207 throw new NullPointerException( "Link name cannot be null!" );
1208 }
1209
1210 if ( DoxiaUtils.isExternalLink( name ) )
1211 {
1212 writeStartTag( BASIC_LINK_TAG, "external-destination", HtmlTools.escapeHTML( name ) );
1213 writeStartTag( INLINE_TAG, "href.external" );
1214 }
1215 else if ( DoxiaUtils.isInternalLink( name ) )
1216 {
1217 String anchor = name.substring( 1 );
1218
1219 if ( !DoxiaUtils.isValidId( anchor ) )
1220 {
1221 anchor = DoxiaUtils.encodeId( anchor, true );
1222
1223 String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
1224 logMessage( "modifiedLink", msg );
1225 }
1226
1227 anchor = "#" + anchor;
1228
1229 writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
1230 writeStartTag( INLINE_TAG, "href.internal" );
1231 }
1232 else
1233 {
1234
1235 String anchor = name;
1236
1237 writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
1238 writeStartTag( INLINE_TAG, "href.internal" );
1239 }
1240 }
1241
1242
1243 public void link( String name )
1244 {
1245 link( name, null );
1246 }
1247
1248
1249 public void link_()
1250 {
1251 writeEndTag( INLINE_TAG );
1252 writeEndTag( BASIC_LINK_TAG );
1253 }
1254
1255
1256 public void italic()
1257 {
1258 writeStartTag( INLINE_TAG, "italic" );
1259 }
1260
1261
1262 public void italic_()
1263 {
1264 writeEndTag( INLINE_TAG );
1265 }
1266
1267
1268 public void bold()
1269 {
1270 writeStartTag( INLINE_TAG, "bold" );
1271 }
1272
1273
1274 public void bold_()
1275 {
1276 writeEndTag( INLINE_TAG );
1277 }
1278
1279
1280 public void monospaced()
1281 {
1282 writeStartTag( INLINE_TAG, "monospace" );
1283 }
1284
1285
1286 public void monospaced_()
1287 {
1288 writeEndTag( INLINE_TAG );
1289 }
1290
1291
1292 public void lineBreak( SinkEventAttributes attributes )
1293 {
1294 writeEOL();
1295 writeEOL();
1296 writeSimpleTag( BLOCK_TAG );
1297 }
1298
1299
1300 public void lineBreak()
1301 {
1302 lineBreak( null );
1303 }
1304
1305
1306 public void nonBreakingSpace()
1307 {
1308 write( " " );
1309 }
1310
1311
1312 public void text( String text, SinkEventAttributes attributes )
1313 {
1314 content( text );
1315 }
1316
1317
1318 public void text( String text )
1319 {
1320 text( text, null );
1321 }
1322
1323
1324 public void rawText( String text )
1325 {
1326 write( text );
1327 }
1328
1329
1330 public void flush()
1331 {
1332 out.flush();
1333 }
1334
1335
1336 public void close()
1337 {
1338 out.close();
1339
1340 if ( getLog().isWarnEnabled() && this.warnMessages != null )
1341 {
1342 for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
1343 {
1344 for ( String msg : entry.getValue() )
1345 {
1346 getLog().warn( msg );
1347 }
1348 }
1349
1350 this.warnMessages = null;
1351 }
1352
1353 init();
1354 }
1355
1356
1357
1358
1359
1360
1361
1362 public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1363 {
1364 String msg = "Unknown Sink event: '" + name + "', ignoring!";
1365 logMessage( "unknownEvent", msg );
1366 }
1367
1368
1369 public void comment( String comment )
1370 {
1371 if ( comment != null )
1372 {
1373 final String originalComment = comment;
1374
1375
1376 while ( comment.contains( "--" ) )
1377 {
1378 comment = comment.replace( "--", "- -" );
1379 }
1380
1381 if ( comment.endsWith( "-" ) )
1382 {
1383 comment += " ";
1384 }
1385
1386 if ( !originalComment.equals( comment ) )
1387 {
1388 String msg = "Modified invalid comment: '" + originalComment + "' to '" + comment + "'";
1389 logMessage( "modifyComment", msg );
1390 }
1391
1392 final StringBuilder buffer = new StringBuilder( comment.length() + 7 );
1393
1394 buffer.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS );
1395 buffer.append( comment );
1396 buffer.append( MINUS ).append( MINUS ).append( GREATER_THAN );
1397
1398 write( buffer.toString() );
1399 }
1400 }
1401
1402
1403
1404
1405 public void beginDocument()
1406 {
1407 write( "<?xml version=\"1.0\"" );
1408 if ( encoding != null )
1409 {
1410 write( " encoding=\"" + encoding + "\"" );
1411 }
1412 write( "?>" );
1413 writeEOL();
1414
1415 MutableAttributeSet atts = new SinkEventAttributeSet();
1416 atts.addAttribute( "xmlns:" + getNameSpace(), FO_NAMESPACE );
1417
1418 if ( languageId != null )
1419 {
1420 atts.addAttribute( "language", languageId );
1421 }
1422
1423 writeStartTag( ROOT_TAG, atts );
1424
1425 writeStartTag( LAYOUT_MASTER_SET_TAG );
1426
1427 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.cover-page" );
1428 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.cover-page.region-body" );
1429 writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1430 writeEOL();
1431
1432 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.toc" );
1433 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.toc.region-body" );
1434 writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.toc.region-before" );
1435 writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.toc.region-after" );
1436 writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1437 writeEOL();
1438
1439 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.body" );
1440 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.body.region-body" );
1441 writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.body.region-before" );
1442 writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.body.region-after" );
1443 writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1444 writeEOL();
1445
1446 writeEndTag( LAYOUT_MASTER_SET_TAG );
1447 writeEOL();
1448
1449 pdfBookmarks();
1450 }
1451
1452
1453
1454
1455 public void endDocument()
1456 {
1457 writeEndTag( ROOT_TAG );
1458 writeEOL();
1459
1460 flush();
1461 close();
1462 }
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473 protected FoConfiguration getFoConfiguration()
1474 {
1475 return config;
1476 }
1477
1478
1479
1480
1481
1482
1483
1484 protected void writeStartTag( Tag tag, String attributeId )
1485 {
1486 writeEOL();
1487 writeStartTag( tag, config.getAttributeSet( attributeId ) );
1488 }
1489
1490
1491
1492
1493
1494
1495
1496
1497 protected void writeStartTag( Tag tag, String id, String name )
1498 {
1499 writeEOL();
1500 MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
1501
1502 writeStartTag( tag, att );
1503 }
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513 protected void writeStartTag( Tag tag, String id, String name, String attributeId )
1514 {
1515 MutableAttributeSet att = config.getAttributeSet( attributeId );
1516
1517
1518 if ( att.isDefined( id ) )
1519 {
1520 att.removeAttribute( id );
1521 }
1522
1523 att.addAttribute( id, name );
1524
1525 writeEOL();
1526 writeStartTag( tag, att );
1527 }
1528
1529
1530
1531
1532
1533
1534
1535
1536 protected void writeEmptyTag( Tag tag, String id, String name )
1537 {
1538 MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
1539
1540 writeEOL();
1541 writeSimpleTag( tag, att );
1542 }
1543
1544
1545
1546
1547
1548
1549
1550 protected void writeEmptyTag( Tag tag, String attributeId )
1551 {
1552 writeEOL();
1553 writeSimpleTag( tag, config.getAttributeSet( attributeId ) );
1554 }
1555
1556
1557
1558
1559
1560
1561 protected void write( String text )
1562 {
1563 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1564 {
1565 this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) );
1566 }
1567 else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
1568 {
1569 this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
1570 }
1571 else
1572 {
1573 out.write( unifyEOLs( text ) );
1574 }
1575 }
1576
1577
1578
1579
1580
1581
1582 protected void writeln( String text )
1583 {
1584 write( text );
1585 writeEOL();
1586 }
1587
1588
1589
1590
1591
1592
1593 protected void content( String text )
1594 {
1595 write( escaped( text, verbatim ) );
1596 }
1597
1598
1599
1600
1601
1602
1603
1604
1605 public static String escaped( String text, boolean verb )
1606 {
1607 int length = text.length();
1608 StringBuilder buffer = new StringBuilder( length );
1609
1610 for ( int i = 0; i < length; ++i )
1611 {
1612 char c = text.charAt( i );
1613 switch ( c )
1614 {
1615 case ' ':
1616 if ( verb )
1617 {
1618 buffer.append( " " );
1619 }
1620 else
1621 {
1622 buffer.append( c );
1623 }
1624 break;
1625 case '<':
1626 buffer.append( "<" );
1627 break;
1628 case '>':
1629 buffer.append( ">" );
1630 break;
1631 case '&':
1632 buffer.append( "&" );
1633 break;
1634 case '\n':
1635 buffer.append( EOL );
1636 if ( verb )
1637 {
1638 buffer.append( "<fo:block/>" + EOL );
1639 }
1640 break;
1641 default:
1642 if ( needsSymbolFont( c ) )
1643 {
1644
1645 buffer.append( "<fo:inline font-family=\"Symbol\">" ).append( c ).append( "</fo:inline>" );
1646 }
1647 else
1648 {
1649 buffer.append( c );
1650 }
1651 }
1652 }
1653
1654 return buffer.toString();
1655 }
1656
1657
1658 protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
1659 {
1660 if ( this.tableCaptionXMLWriterStack.isEmpty() )
1661 {
1662 super.writeStartTag ( t, att, isSimpleTag );
1663 }
1664 else
1665 {
1666 String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
1667 this.tableCaptionXMLWriterStack.getLast().startElement( tag );
1668
1669 if ( att != null )
1670 {
1671 Enumeration<?> names = att.getAttributeNames();
1672 while ( names.hasMoreElements() )
1673 {
1674 Object key = names.nextElement();
1675 Object value = att.getAttribute( key );
1676
1677 this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
1678 }
1679 }
1680
1681 if ( isSimpleTag )
1682 {
1683 this.tableCaptionXMLWriterStack.getLast().endElement();
1684 }
1685 }
1686 }
1687
1688
1689 protected void writeEndTag( Tag t )
1690 {
1691 if ( this.tableCaptionXMLWriterStack.isEmpty() )
1692 {
1693 super.writeEndTag( t );
1694 }
1695 else
1696 {
1697 this.tableCaptionXMLWriterStack.getLast().endElement();
1698 }
1699 }
1700
1701 private static final char UPPER_ALPHA = 0x391;
1702 private static final char PIV = 0x3d6;
1703 private static final char OLINE = 0x203e;
1704 private static final char DIAMS = 0x2666;
1705 private static final char EURO = 0x20ac;
1706 private static final char TRADE = 0x2122;
1707 private static final char PRIME = 0x2032;
1708 private static final char PPRIME = 0x2033;
1709
1710 private static boolean needsSymbolFont( char c )
1711 {
1712
1713
1714
1715
1716 return ( c >= UPPER_ALPHA && c <= PIV )
1717 || ( c == PRIME || c == PPRIME )
1718 || ( c >= OLINE && c <= DIAMS && c != EURO && c != TRADE );
1719 }
1720
1721
1722
1723
1724
1725
1726
1727
1728 protected void startPageSequence( String initPageNumber, String headerText, String footerText )
1729 {
1730 writeln( "<fo:page-sequence initial-page-number=\"" + initPageNumber + "\" master-reference=\"body\">" );
1731 regionBefore( headerText );
1732 regionAfter( footerText );
1733 writeln( "<fo:flow flow-name=\"xsl-region-body\">" );
1734 chapterHeading( null, true );
1735 }
1736
1737
1738
1739
1740
1741
1742 protected void regionBefore( String headerText )
1743 {
1744
1745 }
1746
1747
1748
1749
1750
1751
1752 protected void regionAfter( String footerText )
1753 {
1754
1755 }
1756
1757
1758
1759
1760
1761
1762
1763 protected void chapterHeading( String headerText, boolean chapterNumber )
1764 {
1765
1766 }
1767
1768
1769
1770
1771 protected void pdfBookmarks()
1772 {
1773
1774 }
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784 protected void logMessage( String key, String msg )
1785 {
1786 msg = "[FO Sink] " + msg;
1787 if ( getLog().isDebugEnabled() )
1788 {
1789 getLog().debug( msg );
1790
1791 return;
1792 }
1793
1794 if ( warnMessages == null )
1795 {
1796 warnMessages = new HashMap<String, Set<String>>();
1797 }
1798
1799 Set<String> set = warnMessages.get( key );
1800 if ( set == null )
1801 {
1802 set = new TreeSet<String>();
1803 }
1804 set.add( msg );
1805 warnMessages.put( key, set );
1806 }
1807
1808
1809 protected void init()
1810 {
1811 super.init();
1812
1813 this.listStack.clear();
1814 this.tableGridStack.clear();
1815 this.cellJustifStack.clear();
1816 this.isCellJustifStack.clear();
1817 this.cellCountStack.clear();
1818 this.tableContentWriterStack.clear();
1819 this.tableCaptionWriterStack.clear();
1820 this.tableCaptionXMLWriterStack.clear();
1821 this.tableCaptionStack.clear();
1822
1823 this.section = 0;
1824 this.subsection = 0;
1825 this.subsubsection = 0;
1826 this.verbatim = false;
1827 this.inFigure = false;
1828 this.warnMessages = null;
1829 }
1830 }