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