1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.sink.impl;
20
21 import javax.swing.text.MutableAttributeSet;
22 import javax.swing.text.html.HTML.Tag;
23
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.EmptyStackException;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Stack;
36 import java.util.regex.Pattern;
37
38 import org.apache.commons.lang3.StringUtils;
39 import org.apache.maven.doxia.markup.HtmlMarkup;
40 import org.apache.maven.doxia.markup.Markup;
41 import org.apache.maven.doxia.sink.Sink;
42 import org.apache.maven.doxia.sink.SinkEventAttributes;
43 import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink.VerbatimMode;
44 import org.apache.maven.doxia.util.DoxiaUtils;
45 import org.apache.maven.doxia.util.HtmlTools;
46 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50
51
52
53 public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
54 private static final Logger LOGGER = LoggerFactory.getLogger(Xhtml5BaseSink.class);
55
56
57
58
59
60
61 private final PrintWriter writer;
62
63
64 private static final Pattern HIDDEN_CLASS_PATTERN = Pattern.compile("(?:.*\\s|^)hidden(?:\\s.*|$)");
65
66
67 private StringBuffer textBuffer = new StringBuffer();
68
69
70 private boolean headFlag;
71
72
73 protected Stack<Tag> contentStack = new Stack<>();
74
75
76 protected Stack<List<Tag>> inlineStack = new Stack<>();
77
78
79 private boolean paragraphFlag;
80
81 protected enum VerbatimMode {
82
83 OFF,
84
85 ON,
86
87 ON_WITH_CODE
88 }
89
90 private VerbatimMode verbatimMode;
91
92
93 private final LinkedList<int[]> cellJustifStack;
94
95
96 private final LinkedList<Boolean> isCellJustifStack;
97
98
99 private final LinkedList<Integer> cellCountStack;
100
101
102 private boolean evenTableRow = true;
103
104
105 private final LinkedList<StringWriter> tableContentWriterStack;
106
107 private final LinkedList<StringWriter> tableCaptionWriterStack;
108
109 private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
110
111
112 private final LinkedList<String> tableCaptionStack;
113
114
115 protected MutableAttributeSet tableAttributes;
116
117
118
119
120
121
122
123
124
125
126 public Xhtml5BaseSink(Writer out) {
127 this.writer = new PrintWriter(out);
128
129 this.cellJustifStack = new LinkedList<>();
130 this.isCellJustifStack = new LinkedList<>();
131 this.cellCountStack = new LinkedList<>();
132 this.tableContentWriterStack = new LinkedList<>();
133 this.tableCaptionWriterStack = new LinkedList<>();
134 this.tableCaptionXMLWriterStack = new LinkedList<>();
135 this.tableCaptionStack = new LinkedList<>();
136
137 init();
138 }
139
140
141
142
143
144
145
146
147
148
149 protected StringBuffer getTextBuffer() {
150 return this.textBuffer;
151 }
152
153
154
155
156
157
158 protected void setHeadFlag(boolean headFlag) {
159 this.headFlag = headFlag;
160 }
161
162
163
164
165
166
167 protected boolean isHeadFlag() {
168 return this.headFlag;
169 }
170
171
172
173
174
175 protected VerbatimMode getVerbatimMode() {
176 return this.verbatimMode;
177 }
178
179
180
181
182
183
184 protected void setVerbatimMode(VerbatimMode mode) {
185 this.verbatimMode = mode;
186 }
187
188
189
190
191
192 protected boolean isVerbatim() {
193 return this.verbatimMode != VerbatimMode.OFF;
194 }
195
196
197
198
199
200
201 protected void setCellJustif(int[] justif) {
202 this.cellJustifStack.addLast(justif);
203 this.isCellJustifStack.addLast(Boolean.TRUE);
204 }
205
206
207
208
209
210
211 protected int[] getCellJustif() {
212 return this.cellJustifStack.getLast();
213 }
214
215
216
217
218
219
220 protected void setCellCount(int count) {
221 this.cellCountStack.addLast(count);
222 }
223
224
225
226
227
228
229 protected int getCellCount() {
230 return this.cellCountStack.getLast();
231 }
232
233
234 @Override
235 protected void init() {
236 super.init();
237
238 resetTextBuffer();
239
240 this.cellJustifStack.clear();
241 this.isCellJustifStack.clear();
242 this.cellCountStack.clear();
243 this.tableContentWriterStack.clear();
244 this.tableCaptionWriterStack.clear();
245 this.tableCaptionXMLWriterStack.clear();
246 this.tableCaptionStack.clear();
247 this.inlineStack.clear();
248
249 this.headFlag = false;
250 this.paragraphFlag = false;
251 this.verbatimMode = VerbatimMode.OFF;
252
253 this.evenTableRow = true;
254 this.tableAttributes = null;
255 }
256
257
258
259
260 protected void resetTextBuffer() {
261 this.textBuffer = new StringBuffer();
262 }
263
264
265
266
267
268
269 @Override
270 public void article(SinkEventAttributes attributes) {
271 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
272
273 writeStartTag(HtmlMarkup.ARTICLE, atts);
274 }
275
276
277 @Override
278 public void article_() {
279 writeEndTag(HtmlMarkup.ARTICLE);
280 }
281
282
283 @Override
284 public void navigation(SinkEventAttributes attributes) {
285 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
286
287 writeStartTag(HtmlMarkup.NAV, atts);
288 }
289
290
291 @Override
292 public void navigation_() {
293 writeEndTag(HtmlMarkup.NAV);
294 }
295
296
297 @Override
298 public void sidebar(SinkEventAttributes attributes) {
299 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
300
301 writeStartTag(HtmlMarkup.ASIDE, atts);
302 }
303
304
305 @Override
306 public void sidebar_() {
307 writeEndTag(HtmlMarkup.ASIDE);
308 }
309
310
311 @Override
312 public void section(int level, SinkEventAttributes attributes) {
313 onSection(level, attributes);
314 }
315
316
317 @Override
318 public void sectionTitle(int level, SinkEventAttributes attributes) {
319 onSectionTitle(level, attributes);
320 }
321
322
323 @Override
324 public void sectionTitle_(int level) {
325 onSectionTitle_(level);
326 }
327
328
329 @Override
330 public void section_(int level) {
331 onSection_(level);
332 }
333
334
335
336
337
338
339
340 protected void onSection(int depth, SinkEventAttributes attributes) {
341 if (depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_6) {
342 MutableAttributeSet att = new SinkEventAttributeSet();
343 att.addAttributes(SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES));
344
345 writeStartTag(HtmlMarkup.SECTION, att);
346 }
347 }
348
349
350
351
352
353
354
355 protected void onSection_(int depth) {
356 if (depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_6) {
357 writeEndTag(HtmlMarkup.SECTION);
358 }
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373 protected void onSectionTitle(int depth, SinkEventAttributes attributes) {
374 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
375
376 if (depth == SECTION_LEVEL_1) {
377 writeStartTag(HtmlMarkup.H1, atts);
378 } else if (depth == SECTION_LEVEL_2) {
379 writeStartTag(HtmlMarkup.H2, atts);
380 } else if (depth == SECTION_LEVEL_3) {
381 writeStartTag(HtmlMarkup.H3, atts);
382 } else if (depth == SECTION_LEVEL_4) {
383 writeStartTag(HtmlMarkup.H4, atts);
384 } else if (depth == SECTION_LEVEL_5) {
385 writeStartTag(HtmlMarkup.H5, atts);
386 } else if (depth == SECTION_LEVEL_6) {
387 writeStartTag(HtmlMarkup.H6, atts);
388 }
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402 protected void onSectionTitle_(int depth) {
403 if (depth == SECTION_LEVEL_1) {
404 writeEndTag(HtmlMarkup.H1);
405 } else if (depth == SECTION_LEVEL_2) {
406 writeEndTag(HtmlMarkup.H2);
407 } else if (depth == SECTION_LEVEL_3) {
408 writeEndTag(HtmlMarkup.H3);
409 } else if (depth == SECTION_LEVEL_4) {
410 writeEndTag(HtmlMarkup.H4);
411 } else if (depth == SECTION_LEVEL_5) {
412 writeEndTag(HtmlMarkup.H5);
413 } else if (depth == SECTION_LEVEL_6) {
414 writeEndTag(HtmlMarkup.H6);
415 }
416 }
417
418
419 @Override
420 public void header(SinkEventAttributes attributes) {
421 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
422
423 writeStartTag(HtmlMarkup.HEADER, atts);
424 }
425
426
427 @Override
428 public void header_() {
429 writeEndTag(HtmlMarkup.HEADER);
430 }
431
432
433 @Override
434 public void content(SinkEventAttributes attributes) {
435 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
436
437 if (contentStack.empty()) {
438 writeStartTag(contentStack.push(HtmlMarkup.MAIN), atts);
439 } else {
440 if (atts == null) {
441 atts = new SinkEventAttributeSet(1);
442 }
443
444 String divClass = "content";
445 if (atts.isDefined(SinkEventAttributes.CLASS)) {
446 divClass += " " + atts.getAttribute(SinkEventAttributes.CLASS).toString();
447 }
448
449 atts.addAttribute(SinkEventAttributes.CLASS, divClass);
450
451 writeStartTag(contentStack.push(HtmlMarkup.DIV), atts);
452 }
453 }
454
455
456 @Override
457 public void content_() {
458 try {
459 writeEndTag(contentStack.pop());
460 } catch (EmptyStackException ese) {
461
462 }
463 }
464
465
466 @Override
467 public void footer(SinkEventAttributes attributes) {
468 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
469
470 writeStartTag(HtmlMarkup.FOOTER, atts);
471 }
472
473
474 @Override
475 public void footer_() {
476 writeEndTag(HtmlMarkup.FOOTER);
477 }
478
479
480
481
482
483
484
485
486
487 @Override
488 public void list(SinkEventAttributes attributes) {
489 if (paragraphFlag) {
490
491
492
493 paragraph_();
494 }
495
496 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
497
498 writeStartTag(HtmlMarkup.UL, atts);
499 }
500
501
502
503
504
505 @Override
506 public void list_() {
507 writeEndTag(HtmlMarkup.UL);
508 }
509
510
511
512
513
514 @Override
515 public void listItem(SinkEventAttributes attributes) {
516 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
517
518 writeStartTag(HtmlMarkup.LI, atts);
519 }
520
521
522
523
524
525 @Override
526 public void listItem_() {
527 writeEndTag(HtmlMarkup.LI);
528 }
529
530
531
532
533
534
535
536 @Override
537 public void numberedList(int numbering, SinkEventAttributes attributes) {
538 if (paragraphFlag) {
539
540
541
542 paragraph_();
543 }
544
545 String olStyle = "list-style-type: ";
546 switch (numbering) {
547 case NUMBERING_UPPER_ALPHA:
548 olStyle += "upper-alpha";
549 break;
550 case NUMBERING_LOWER_ALPHA:
551 olStyle += "lower-alpha";
552 break;
553 case NUMBERING_UPPER_ROMAN:
554 olStyle += "upper-roman";
555 break;
556 case NUMBERING_LOWER_ROMAN:
557 olStyle += "lower-roman";
558 break;
559 case NUMBERING_DECIMAL:
560 default:
561 olStyle += "decimal";
562 }
563 olStyle += ";";
564
565 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
566
567 if (atts == null) {
568 atts = new SinkEventAttributeSet(1);
569 }
570
571 if (atts.isDefined(SinkEventAttributes.STYLE)) {
572 olStyle += " " + atts.getAttribute(SinkEventAttributes.STYLE).toString();
573 }
574
575 atts.addAttribute(SinkEventAttributes.STYLE, olStyle);
576
577 writeStartTag(HtmlMarkup.OL, atts);
578 }
579
580
581
582
583
584 @Override
585 public void numberedList_() {
586 writeEndTag(HtmlMarkup.OL);
587 }
588
589
590
591
592
593 @Override
594 public void numberedListItem(SinkEventAttributes attributes) {
595 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
596
597 writeStartTag(HtmlMarkup.LI, atts);
598 }
599
600
601
602
603
604 @Override
605 public void numberedListItem_() {
606 writeEndTag(HtmlMarkup.LI);
607 }
608
609
610
611
612
613 @Override
614 public void definitionList(SinkEventAttributes attributes) {
615 if (paragraphFlag) {
616
617
618
619 paragraph_();
620 }
621
622 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
623
624 writeStartTag(HtmlMarkup.DL, atts);
625 }
626
627
628
629
630
631 @Override
632 public void definitionList_() {
633 writeEndTag(HtmlMarkup.DL);
634 }
635
636
637
638
639
640 @Override
641 public void definedTerm(SinkEventAttributes attributes) {
642 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
643
644 writeStartTag(HtmlMarkup.DT, atts);
645 }
646
647
648
649
650
651 @Override
652 public void definedTerm_() {
653 writeEndTag(HtmlMarkup.DT);
654 }
655
656
657
658
659
660 @Override
661 public void definition(SinkEventAttributes attributes) {
662 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
663
664 writeStartTag(HtmlMarkup.DD, atts);
665 }
666
667
668
669
670
671 @Override
672 public void definition_() {
673 writeEndTag(HtmlMarkup.DD);
674 }
675
676
677 @Override
678 public void figure(SinkEventAttributes attributes) {
679 writeStartTag(HtmlMarkup.FIGURE, attributes);
680 }
681
682
683 @Override
684 public void figure_() {
685 writeEndTag(HtmlMarkup.FIGURE);
686 }
687
688
689 @Override
690 public void figureGraphics(String src, SinkEventAttributes attributes) {
691 MutableAttributeSet filtered = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_IMG_ATTRIBUTES);
692 if (filtered != null) {
693 filtered.removeAttribute(SinkEventAttributes.SRC.toString());
694 }
695
696 int count = (attributes == null ? 1 : attributes.getAttributeCount() + 1);
697
698 MutableAttributeSet atts = new SinkEventAttributeSet(count);
699
700 atts.addAttribute(SinkEventAttributes.SRC, HtmlTools.escapeHTML(src, true));
701 atts.addAttributes(filtered);
702
703 writeStartTag(HtmlMarkup.IMG, atts, true);
704 }
705
706
707 @Override
708 public void figureCaption(SinkEventAttributes attributes) {
709 writeStartTag(HtmlMarkup.FIGCAPTION, attributes);
710 }
711
712
713 @Override
714 public void figureCaption_() {
715 writeEndTag(HtmlMarkup.FIGCAPTION);
716 }
717
718
719
720
721
722 @Override
723 public void paragraph(SinkEventAttributes attributes) {
724 paragraphFlag = true;
725
726 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
727
728 writeStartTag(HtmlMarkup.P, atts);
729 }
730
731
732
733
734
735 @Override
736 public void paragraph_() {
737 if (paragraphFlag) {
738 writeEndTag(HtmlMarkup.P);
739 paragraphFlag = false;
740 }
741 }
742
743
744 @Override
745 public void data(String value, SinkEventAttributes attributes) {
746 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
747
748 MutableAttributeSet att = new SinkEventAttributeSet();
749 if (value != null) {
750 att.addAttribute(SinkEventAttributes.VALUE, value);
751 }
752 att.addAttributes(atts);
753
754 writeStartTag(HtmlMarkup.DATA, att);
755 }
756
757
758 @Override
759 public void data_() {
760 writeEndTag(HtmlMarkup.DATA);
761 }
762
763
764 @Override
765 public void time(String datetime, SinkEventAttributes attributes) {
766 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
767
768 MutableAttributeSet att = new SinkEventAttributeSet();
769 if (datetime != null) {
770 att.addAttribute("datetime", datetime);
771 }
772 att.addAttributes(atts);
773
774 writeStartTag(HtmlMarkup.TIME, att);
775 }
776
777
778 @Override
779 public void time_() {
780 writeEndTag(HtmlMarkup.TIME);
781 }
782
783
784
785
786
787 @Override
788 public void address(SinkEventAttributes attributes) {
789 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
790
791 writeStartTag(HtmlMarkup.ADDRESS, atts);
792 }
793
794
795
796
797
798 @Override
799 public void address_() {
800 writeEndTag(HtmlMarkup.ADDRESS);
801 }
802
803
804
805
806
807 @Override
808 public void blockquote(SinkEventAttributes attributes) {
809 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
810
811 writeStartTag(HtmlMarkup.BLOCKQUOTE, atts);
812 }
813
814
815
816
817
818 @Override
819 public void blockquote_() {
820 writeEndTag(HtmlMarkup.BLOCKQUOTE);
821 }
822
823
824
825
826
827 @Override
828 public void division(SinkEventAttributes attributes) {
829 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
830
831 writeStartTag(HtmlMarkup.DIV, atts);
832 }
833
834
835
836
837
838 @Override
839 public void division_() {
840 writeEndTag(HtmlMarkup.DIV);
841 }
842
843
844
845
846
847
848
849
850
851
852 @Override
853 public void verbatim(SinkEventAttributes attributes) {
854 if (paragraphFlag) {
855
856
857
858 paragraph_();
859 }
860
861 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES);
862
863 if (atts == null) {
864 atts = new SinkEventAttributeSet();
865 }
866
867 verbatimMode = VerbatimMode.ON;
868 if (atts.isDefined(SinkEventAttributes.DECORATION)) {
869 if ("source"
870 .equals(atts.getAttribute(SinkEventAttributes.DECORATION).toString())) {
871 verbatimMode = VerbatimMode.ON_WITH_CODE;
872 }
873 }
874
875 atts.removeAttribute(SinkEventAttributes.DECORATION);
876
877 writeStartTag(HtmlMarkup.PRE, atts);
878 if (verbatimMode == VerbatimMode.ON_WITH_CODE) {
879 writeStartTag(HtmlMarkup.CODE);
880 }
881 }
882
883
884
885
886
887
888 @Override
889 public void verbatim_() {
890 if (verbatimMode == VerbatimMode.ON_WITH_CODE) {
891 writeEndTag(HtmlMarkup.CODE);
892 }
893 writeEndTag(HtmlMarkup.PRE);
894
895 verbatimMode = VerbatimMode.OFF;
896 }
897
898
899
900
901
902 @Override
903 public void horizontalRule(SinkEventAttributes attributes) {
904 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_HR_ATTRIBUTES);
905
906 writeSimpleTag(HtmlMarkup.HR, atts);
907 }
908
909
910 @Override
911 public void table(SinkEventAttributes attributes) {
912 this.tableContentWriterStack.addLast(new StringWriter());
913
914 if (paragraphFlag) {
915
916
917
918 paragraph_();
919 }
920
921
922 if (attributes == null) {
923 this.tableAttributes = new SinkEventAttributeSet(0);
924 } else {
925 this.tableAttributes = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TABLE_ATTRIBUTES);
926 }
927 }
928
929
930
931
932
933 @Override
934 public void table_() {
935 writeEndTag(HtmlMarkup.TABLE);
936
937 if (!this.cellCountStack.isEmpty()) {
938 this.cellCountStack.removeLast().toString();
939 }
940
941 if (this.tableContentWriterStack.isEmpty()) {
942 LOGGER.warn("{}No table content", getLocationLogPrefix());
943 return;
944 }
945
946 String tableContent = this.tableContentWriterStack.removeLast().toString();
947
948 String tableCaption = null;
949 if (!this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null) {
950 tableCaption = this.tableCaptionStack.removeLast();
951 }
952
953 if (tableCaption != null) {
954
955 StringBuilder sb = new StringBuilder();
956 sb.append(tableContent, 0, tableContent.indexOf(Markup.GREATER_THAN) + 1);
957 sb.append(tableCaption);
958 sb.append(tableContent.substring(tableContent.indexOf(Markup.GREATER_THAN) + 1));
959
960 write(sb.toString());
961 } else {
962 write(tableContent);
963 }
964 }
965
966
967
968
969
970
971
972
973
974 @Override
975 public void tableRows(int[] justification, boolean grid) {
976 setCellJustif(justification);
977
978 MutableAttributeSet att = new SinkEventAttributeSet();
979
980 String tableClass = "bodyTable" + (grid ? " bodyTableBorder" : "");
981 if (this.tableAttributes.isDefined(SinkEventAttributes.CLASS.toString())) {
982 tableClass += " "
983 + this.tableAttributes
984 .getAttribute(SinkEventAttributes.CLASS)
985 .toString();
986 }
987
988 att.addAttribute(SinkEventAttributes.CLASS, tableClass);
989
990 att.addAttributes(this.tableAttributes);
991 this.tableAttributes.removeAttributes(this.tableAttributes);
992
993 writeStartTag(HtmlMarkup.TABLE, att);
994
995 this.cellCountStack.addLast(0);
996 }
997
998
999 @Override
1000 public void tableRows_() {
1001 if (!this.cellJustifStack.isEmpty()) {
1002 this.cellJustifStack.removeLast();
1003 }
1004 if (!this.isCellJustifStack.isEmpty()) {
1005 this.isCellJustifStack.removeLast();
1006 }
1007
1008 this.evenTableRow = true;
1009 }
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019 @Override
1020 public void tableRow(SinkEventAttributes attributes) {
1021 MutableAttributeSet attrs = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TR_ATTRIBUTES);
1022
1023 if (attrs == null) {
1024 attrs = new SinkEventAttributeSet();
1025 }
1026
1027 String rowClass = evenTableRow ? "a" : "b";
1028 boolean hidden = false;
1029 if (attrs.isDefined(SinkEventAttributes.CLASS.toString())) {
1030 String givenRowClass = (String) attrs.getAttribute(SinkEventAttributes.CLASS.toString());
1031 if (HIDDEN_CLASS_PATTERN.matcher(givenRowClass).matches()) {
1032 hidden = true;
1033 }
1034 rowClass += " " + givenRowClass;
1035 }
1036
1037 attrs.addAttribute(SinkEventAttributes.CLASS, rowClass);
1038
1039 writeStartTag(HtmlMarkup.TR, attrs);
1040
1041 if (!hidden) {
1042 evenTableRow = !evenTableRow;
1043 }
1044
1045 if (!this.cellCountStack.isEmpty()) {
1046 this.cellCountStack.removeLast();
1047 this.cellCountStack.addLast(0);
1048 }
1049 }
1050
1051
1052
1053
1054
1055 @Override
1056 public void tableRow_() {
1057 writeEndTag(HtmlMarkup.TR);
1058 }
1059
1060
1061 @Override
1062 public void tableCell(SinkEventAttributes attributes) {
1063 tableCell(false, attributes);
1064 }
1065
1066
1067 @Override
1068 public void tableHeaderCell(SinkEventAttributes attributes) {
1069 tableCell(true, attributes);
1070 }
1071
1072
1073
1074
1075
1076
1077
1078 private void tableCell(boolean headerRow, MutableAttributeSet attributes) {
1079 Tag t = (headerRow ? HtmlMarkup.TH : HtmlMarkup.TD);
1080
1081 if (!headerRow
1082 && cellCountStack != null
1083 && !cellCountStack.isEmpty()
1084 && cellJustifStack != null
1085 && !cellJustifStack.isEmpty()
1086 && getCellJustif() != null) {
1087 int cellCount = getCellCount();
1088 int[] cellJustif = getCellJustif();
1089 if (cellCount < cellJustif.length) {
1090 if (attributes == null) {
1091 attributes = new SinkEventAttributeSet();
1092 }
1093 Map<Integer, String> hash = new HashMap<>();
1094 hash.put(Sink.JUSTIFY_CENTER, "center");
1095 hash.put(Sink.JUSTIFY_LEFT, "left");
1096 hash.put(Sink.JUSTIFY_RIGHT, "right");
1097
1098 String tdStyle = "text-align: " + hash.get(cellJustif[cellCount]) + ";";
1099 if (attributes.isDefined(SinkEventAttributes.STYLE)) {
1100 tdStyle += " "
1101 + attributes.getAttribute(SinkEventAttributes.STYLE).toString();
1102 }
1103
1104 attributes.addAttribute(SinkEventAttributes.STYLE, tdStyle);
1105 }
1106 }
1107
1108 if (attributes == null) {
1109 writeStartTag(t, null);
1110 } else {
1111 writeStartTag(t, SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TD_ATTRIBUTES));
1112 }
1113 }
1114
1115
1116 @Override
1117 public void tableCell_() {
1118 tableCell_(false);
1119 }
1120
1121
1122 @Override
1123 public void tableHeaderCell_() {
1124 tableCell_(true);
1125 }
1126
1127
1128
1129
1130
1131
1132
1133
1134 private void tableCell_(boolean headerRow) {
1135 Tag t = (headerRow ? HtmlMarkup.TH : HtmlMarkup.TD);
1136
1137 writeEndTag(t);
1138
1139 if (!this.isCellJustifStack.isEmpty()
1140 && this.isCellJustifStack.getLast().equals(Boolean.TRUE)
1141 && !this.cellCountStack.isEmpty()) {
1142 int cellCount = Integer.parseInt(this.cellCountStack.removeLast().toString());
1143 this.cellCountStack.addLast(++cellCount);
1144 }
1145 }
1146
1147
1148
1149
1150
1151 @Override
1152 public void tableCaption(SinkEventAttributes attributes) {
1153 StringWriter sw = new StringWriter();
1154 this.tableCaptionWriterStack.addLast(sw);
1155 this.tableCaptionXMLWriterStack.addLast(new PrettyPrintXMLWriter(sw));
1156
1157
1158 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
1159
1160 writeStartTag(HtmlMarkup.CAPTION, atts);
1161 }
1162
1163
1164
1165
1166
1167 @Override
1168 public void tableCaption_() {
1169 writeEndTag(HtmlMarkup.CAPTION);
1170
1171 if (!this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null) {
1172 this.tableCaptionStack.addLast(
1173 this.tableCaptionWriterStack.removeLast().toString());
1174 this.tableCaptionXMLWriterStack.removeLast();
1175 }
1176 }
1177
1178
1179
1180
1181
1182 @Override
1183 public void anchor(String name, SinkEventAttributes attributes) {
1184 Objects.requireNonNull(name, "name cannot be null");
1185
1186 if (headFlag) {
1187 return;
1188 }
1189
1190 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
1191
1192 String id = name;
1193
1194 if (!DoxiaUtils.isValidId(id)) {
1195 id = DoxiaUtils.encodeId(name);
1196
1197 LOGGER.debug("{}Modified invalid anchor name '{}' to '{}'", getLocationLogPrefix(), name, id);
1198 }
1199
1200 MutableAttributeSet att = new SinkEventAttributeSet();
1201 att.addAttribute(SinkEventAttributes.ID, id);
1202 att.addAttributes(atts);
1203
1204 writeStartTag(HtmlMarkup.A, att);
1205 }
1206
1207
1208
1209
1210
1211 @Override
1212 public void anchor_() {
1213 if (!headFlag) {
1214 writeEndTag(HtmlMarkup.A);
1215 }
1216 }
1217
1218
1219
1220
1221
1222
1223
1224 @Override
1225 public void link(String name, SinkEventAttributes attributes) {
1226 Objects.requireNonNull(name, "name cannot be null");
1227
1228 if (headFlag) {
1229 return;
1230 }
1231
1232 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_LINK_ATTRIBUTES);
1233
1234 if (atts == null) {
1235 atts = new SinkEventAttributeSet();
1236 }
1237
1238 if (DoxiaUtils.isExternalLink(name)) {
1239 String linkClass = "externalLink";
1240 if (atts.isDefined(SinkEventAttributes.CLASS.toString())) {
1241 String givenLinkClass = (String) atts.getAttribute(SinkEventAttributes.CLASS.toString());
1242 linkClass += " " + givenLinkClass;
1243 }
1244
1245 atts.addAttribute(SinkEventAttributes.CLASS, linkClass);
1246 }
1247
1248 atts.addAttribute(SinkEventAttributes.HREF, HtmlTools.escapeHTML(name));
1249
1250 writeStartTag(HtmlMarkup.A, atts);
1251 }
1252
1253
1254
1255
1256
1257 @Override
1258 public void link_() {
1259 if (!headFlag) {
1260 writeEndTag(HtmlMarkup.A);
1261 }
1262 }
1263
1264 private void inlineSemantics(SinkEventAttributes attributes, String semantic, List<Tag> tags, Tag tag) {
1265 if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, semantic)) {
1266 SinkEventAttributes attributesNoSemantics = (SinkEventAttributes) attributes.copyAttributes();
1267 attributesNoSemantics.removeAttribute(SinkEventAttributes.SEMANTICS);
1268 writeStartTag(tag, attributesNoSemantics);
1269 tags.add(0, tag);
1270 }
1271 }
1272
1273
1274 @Override
1275 public void inline(SinkEventAttributes attributes) {
1276 if (!headFlag) {
1277 List<Tag> tags = new ArrayList<>();
1278
1279 if (attributes != null) {
1280 inlineSemantics(attributes, "emphasis", tags, HtmlMarkup.EM);
1281 inlineSemantics(attributes, "strong", tags, HtmlMarkup.STRONG);
1282 inlineSemantics(attributes, "small", tags, HtmlMarkup.SMALL);
1283 inlineSemantics(attributes, "line-through", tags, HtmlMarkup.S);
1284 inlineSemantics(attributes, "citation", tags, HtmlMarkup.CITE);
1285 inlineSemantics(attributes, "quote", tags, HtmlMarkup.Q);
1286 inlineSemantics(attributes, "definition", tags, HtmlMarkup.DFN);
1287 inlineSemantics(attributes, "abbreviation", tags, HtmlMarkup.ABBR);
1288 inlineSemantics(attributes, "italic", tags, HtmlMarkup.I);
1289 inlineSemantics(attributes, "bold", tags, HtmlMarkup.B);
1290 inlineSemantics(attributes, "code", tags, HtmlMarkup.CODE);
1291 inlineSemantics(attributes, "variable", tags, HtmlMarkup.VAR);
1292 inlineSemantics(attributes, "sample", tags, HtmlMarkup.SAMP);
1293 inlineSemantics(attributes, "keyboard", tags, HtmlMarkup.KBD);
1294 inlineSemantics(attributes, "superscript", tags, HtmlMarkup.SUP);
1295 inlineSemantics(attributes, "subscript", tags, HtmlMarkup.SUB);
1296 inlineSemantics(attributes, "annotation", tags, HtmlMarkup.U);
1297 inlineSemantics(attributes, "highlight", tags, HtmlMarkup.MARK);
1298 inlineSemantics(attributes, "ruby", tags, HtmlMarkup.RUBY);
1299 inlineSemantics(attributes, "rubyBase", tags, HtmlMarkup.RB);
1300 inlineSemantics(attributes, "rubyText", tags, HtmlMarkup.RT);
1301 inlineSemantics(attributes, "rubyTextContainer", tags, HtmlMarkup.RTC);
1302 inlineSemantics(attributes, "rubyParentheses", tags, HtmlMarkup.RP);
1303 inlineSemantics(attributes, "bidirectionalIsolation", tags, HtmlMarkup.BDI);
1304 inlineSemantics(attributes, "bidirectionalOverride", tags, HtmlMarkup.BDO);
1305 inlineSemantics(attributes, "phrase", tags, HtmlMarkup.SPAN);
1306 inlineSemantics(attributes, "insert", tags, HtmlMarkup.INS);
1307 inlineSemantics(attributes, "delete", tags, HtmlMarkup.DEL);
1308 }
1309
1310 inlineStack.push(tags);
1311 }
1312 }
1313
1314
1315 @Override
1316 public void inline_() {
1317 if (!headFlag) {
1318 for (Tag tag : inlineStack.pop()) {
1319 writeEndTag(tag);
1320 }
1321 }
1322 }
1323
1324
1325
1326
1327
1328 @Override
1329 public void italic() {
1330 inline(SinkEventAttributeSet.Semantics.ITALIC);
1331 }
1332
1333
1334
1335
1336
1337 @Override
1338 public void italic_() {
1339 inline_();
1340 }
1341
1342
1343
1344
1345
1346 @Override
1347 public void bold() {
1348 inline(SinkEventAttributeSet.Semantics.BOLD);
1349 }
1350
1351
1352
1353
1354
1355 @Override
1356 public void bold_() {
1357 inline_();
1358 }
1359
1360
1361
1362
1363
1364 @Override
1365 public void monospaced() {
1366 inline(SinkEventAttributeSet.Semantics.CODE);
1367 }
1368
1369
1370
1371
1372
1373 @Override
1374 public void monospaced_() {
1375 inline_();
1376 }
1377
1378
1379
1380
1381
1382 @Override
1383 public void lineBreak(SinkEventAttributes attributes) {
1384 if (headFlag || isVerbatim()) {
1385 getTextBuffer().append(EOL);
1386 } else {
1387 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BR_ATTRIBUTES);
1388
1389 writeSimpleTag(HtmlMarkup.BR, atts);
1390 }
1391 }
1392
1393
1394 @Override
1395 public void lineBreakOpportunity(SinkEventAttributes attributes) {
1396 if (!headFlag && !isVerbatim()) {
1397 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BR_ATTRIBUTES);
1398
1399 writeSimpleTag(HtmlMarkup.WBR, atts);
1400 }
1401 }
1402
1403
1404 @Override
1405 public void pageBreak() {
1406 comment(" PB ");
1407 }
1408
1409
1410 @Override
1411 public void nonBreakingSpace() {
1412 if (headFlag) {
1413 getTextBuffer().append(' ');
1414 } else {
1415 write(" ");
1416 }
1417 }
1418
1419
1420 @Override
1421 public void text(String text, SinkEventAttributes attributes) {
1422 if (attributes != null) {
1423 inline(attributes);
1424 }
1425 if (headFlag) {
1426 getTextBuffer().append(text);
1427 } else if (isVerbatim()) {
1428 verbatimContent(text);
1429 } else {
1430 content(text);
1431 }
1432 if (attributes != null) {
1433 inline_();
1434 }
1435 }
1436
1437
1438 @Override
1439 public void rawText(String text) {
1440 if (headFlag) {
1441 getTextBuffer().append(text);
1442 } else {
1443 write(text);
1444 }
1445 }
1446
1447
1448 @Override
1449 public void comment(String comment) {
1450 if (comment != null) {
1451 final String originalComment = comment;
1452
1453
1454 while (comment.contains("--")) {
1455 comment = comment.replace("--", "- -");
1456 }
1457
1458 if (comment.endsWith("-")) {
1459 comment += " ";
1460 }
1461
1462 if (!originalComment.equals(comment)) {
1463 LOGGER.warn(
1464 "{}Modified invalid comment '{}' to '{}'", getLocationLogPrefix(), originalComment, comment);
1465 }
1466
1467 final StringBuilder buffer = new StringBuilder(comment.length() + 7);
1468
1469 buffer.append(LESS_THAN).append(BANG).append(MINUS).append(MINUS);
1470 buffer.append(comment);
1471 buffer.append(MINUS).append(MINUS).append(GREATER_THAN);
1472
1473 write(buffer.toString());
1474 }
1475 }
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518 @Override
1519 public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
1520 if (requiredParams == null || !(requiredParams[0] instanceof Integer)) {
1521 LOGGER.warn("{}No type information for unknown event '{}', ignoring!", getLocationLogPrefix(), name);
1522
1523 return;
1524 }
1525
1526 int tagType = (Integer) requiredParams[0];
1527
1528 if (tagType == ENTITY_TYPE) {
1529 rawText(name);
1530
1531 return;
1532 }
1533
1534 if (tagType == CDATA_TYPE) {
1535 rawText(EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL);
1536
1537 return;
1538 }
1539
1540 Tag tag = HtmlTools.getHtmlTag(name);
1541
1542 if (tag == null) {
1543 LOGGER.warn("[]No HTML tag found for unknown event '{}', ignoring!", getLocationLogPrefix(), name);
1544 } else {
1545 if (tagType == TAG_TYPE_SIMPLE) {
1546 writeSimpleTag(tag, escapeAttributeValues(attributes));
1547 } else if (tagType == TAG_TYPE_START) {
1548 writeStartTag(tag, escapeAttributeValues(attributes));
1549 } else if (tagType == TAG_TYPE_END) {
1550 writeEndTag(tag);
1551 } else {
1552 LOGGER.warn("{}No type information for unknown event '{}', ignoring!", getLocationLogPrefix(), name);
1553 }
1554 }
1555 }
1556
1557 private SinkEventAttributes escapeAttributeValues(SinkEventAttributes attributes) {
1558 SinkEventAttributeSet set = new SinkEventAttributeSet(attributes.getAttributeCount());
1559
1560 Enumeration<?> names = attributes.getAttributeNames();
1561
1562 while (names.hasMoreElements()) {
1563 Object name = names.nextElement();
1564
1565 set.addAttribute(name, escapeHTML(attributes.getAttribute(name).toString()));
1566 }
1567
1568 return set;
1569 }
1570
1571
1572 @Override
1573 public void flush() {
1574 writer.flush();
1575 }
1576
1577
1578 @Override
1579 public void close() {
1580 writer.close();
1581
1582 init();
1583 }
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594 protected void content(String text) {
1595
1596 String txt = escapeHTML(text);
1597 txt = StringUtils.replace(txt, "&#", "&#");
1598 write(txt);
1599 }
1600
1601
1602
1603
1604
1605
1606 protected void verbatimContent(String text) {
1607 write(escapeHTML(text));
1608 }
1609
1610
1611
1612
1613
1614
1615
1616
1617 protected static String escapeHTML(String text) {
1618 return HtmlTools.escapeHTML(text, false);
1619 }
1620
1621
1622
1623
1624
1625
1626
1627
1628 protected static String encodeURL(String text) {
1629 return HtmlTools.encodeURL(text);
1630 }
1631
1632
1633 protected void write(String text) {
1634 if (!this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null) {
1635 this.tableCaptionXMLWriterStack.getLast().writeMarkup(unifyEOLs(text));
1636 } else if (!this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null) {
1637 this.tableContentWriterStack.getLast().write(unifyEOLs(text));
1638 } else {
1639 writer.write(unifyEOLs(text));
1640 }
1641 }
1642
1643
1644 @Override
1645 protected void writeStartTag(Tag t, MutableAttributeSet att, boolean isSimpleTag) {
1646 if (this.tableCaptionXMLWriterStack.isEmpty()) {
1647 super.writeStartTag(t, att, isSimpleTag);
1648 } else {
1649 String tag = (getNameSpace() != null ? getNameSpace() + ":" : "") + t.toString();
1650 this.tableCaptionXMLWriterStack.getLast().startElement(tag);
1651
1652 if (att != null) {
1653 Enumeration<?> names = att.getAttributeNames();
1654 while (names.hasMoreElements()) {
1655 Object key = names.nextElement();
1656 Object value = att.getAttribute(key);
1657
1658 this.tableCaptionXMLWriterStack.getLast().addAttribute(key.toString(), value.toString());
1659 }
1660 }
1661
1662 if (isSimpleTag) {
1663 this.tableCaptionXMLWriterStack.getLast().endElement();
1664 }
1665 }
1666 }
1667
1668
1669 @Override
1670 protected void writeEndTag(Tag t) {
1671 if (this.tableCaptionXMLWriterStack.isEmpty()) {
1672 super.writeEndTag(t);
1673 } else {
1674 this.tableCaptionXMLWriterStack.getLast().endElement();
1675 }
1676 }
1677 }