View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Abstract base xhtml5 sink implementation.
52   */
53  public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup {
54      private static final Logger LOGGER = LoggerFactory.getLogger(Xhtml5BaseSink.class);
55  
56      // ----------------------------------------------------------------------
57      // Instance fields
58      // ----------------------------------------------------------------------
59  
60      /** The PrintWriter to write the result. */
61      private final PrintWriter writer;
62  
63      /** Used to identify if a class string contains `hidden` */
64      private static final Pattern HIDDEN_CLASS_PATTERN = Pattern.compile("(?:.*\\s|^)hidden(?:\\s.*|$)");
65  
66      /** Used to collect text events mainly for the head events. */
67      private StringBuffer textBuffer = new StringBuffer();
68  
69      /** An indication on if we're inside a head. */
70      private boolean headFlag;
71  
72      /** Keep track of the main and div tags for content events. */
73      protected Stack<Tag> contentStack = new Stack<>();
74  
75      /** Keep track of the closing tags for inline events. */
76      protected Stack<List<Tag>> inlineStack = new Stack<>();
77  
78      /** An indication on if we're inside a paragraph flag. */
79      private boolean paragraphFlag;
80  
81      protected enum VerbatimMode {
82          /** not in verbatim mode */
83          OFF,
84          /** Inside {@code <pre>} */
85          ON,
86          /** Inside {@code <pre><code>} */
87          ON_WITH_CODE
88      }
89      /** An indication on if we're in verbatim mode and if so, surrounded by which tags. */
90      private VerbatimMode verbatimMode;
91  
92      /** Stack of alignment int[] of table cells. */
93      private final LinkedList<int[]> cellJustifStack;
94  
95      /** Stack of justification of table cells. */
96      private final LinkedList<Boolean> isCellJustifStack;
97  
98      /** Stack of current table cell. */
99      private final LinkedList<Integer> cellCountStack;
100 
101     /** Used to style successive table rows differently. */
102     private boolean evenTableRow = true;
103 
104     /** The stack of StringWriter to write the table result temporary, so we could play with the output DOXIA-177. */
105     private final LinkedList<StringWriter> tableContentWriterStack;
106 
107     private final LinkedList<StringWriter> tableCaptionWriterStack;
108 
109     private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
110 
111     /** The stack of table caption */
112     private final LinkedList<String> tableCaptionStack;
113 
114     /** used to store attributes passed to table(). */
115     protected MutableAttributeSet tableAttributes;
116 
117     // ----------------------------------------------------------------------
118     // Constructor
119     // ----------------------------------------------------------------------
120 
121     /**
122      * Constructor, initialize the PrintWriter.
123      *
124      * @param out The writer to write the result.
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     // Accessor methods
142     // ----------------------------------------------------------------------
143 
144     /**
145      * To use mainly when playing with the head events.
146      *
147      * @return the current buffer of text events.
148      */
149     protected StringBuffer getTextBuffer() {
150         return this.textBuffer;
151     }
152 
153     /**
154      * <p>Setter for the field <code>headFlag</code>.</p>
155      *
156      * @param headFlag an header flag.
157      */
158     protected void setHeadFlag(boolean headFlag) {
159         this.headFlag = headFlag;
160     }
161 
162     /**
163      * <p>isHeadFlag.</p>
164      *
165      * @return the current headFlag.
166      */
167     protected boolean isHeadFlag() {
168         return this.headFlag;
169     }
170 
171     /**
172      *
173      * @return the current verbatim mode.
174      */
175     protected VerbatimMode getVerbatimMode() {
176         return this.verbatimMode;
177     }
178 
179     /**
180      * <p>Setter for the field <code>verbatimMode</code>.</p>
181      *
182      * @param mode a verbatim mode.
183      */
184     protected void setVerbatimMode(VerbatimMode mode) {
185         this.verbatimMode = mode;
186     }
187 
188     /**
189      *
190      * @return {@code true} if inside verbatim section, {@code false} otherwise
191      */
192     protected boolean isVerbatim() {
193         return this.verbatimMode != VerbatimMode.OFF;
194     }
195 
196     /**
197      * <p>Setter for the field <code>cellJustif</code>.</p>
198      *
199      * @param justif the new cell justification array.
200      */
201     protected void setCellJustif(int[] justif) {
202         this.cellJustifStack.addLast(justif);
203         this.isCellJustifStack.addLast(Boolean.TRUE);
204     }
205 
206     /**
207      * <p>Getter for the field <code>cellJustif</code>.</p>
208      *
209      * @return the current cell justification array.
210      */
211     protected int[] getCellJustif() {
212         return this.cellJustifStack.getLast();
213     }
214 
215     /**
216      * <p>Setter for the field <code>cellCount</code>.</p>
217      *
218      * @param count the new cell count.
219      */
220     protected void setCellCount(int count) {
221         this.cellCountStack.addLast(count);
222     }
223 
224     /**
225      * <p>Getter for the field <code>cellCount</code>.</p>
226      *
227      * @return the current cell count.
228      */
229     protected int getCellCount() {
230         return this.cellCountStack.getLast();
231     }
232 
233     /** {@inheritDoc} */
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      * Reset the text buffer.
259      */
260     protected void resetTextBuffer() {
261         this.textBuffer = new StringBuffer();
262     }
263 
264     // ----------------------------------------------------------------------
265     // Sections
266     // ----------------------------------------------------------------------
267 
268     /** {@inheritDoc} */
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     /** {@inheritDoc} */
277     @Override
278     public void article_() {
279         writeEndTag(HtmlMarkup.ARTICLE);
280     }
281 
282     /** {@inheritDoc} */
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     /** {@inheritDoc} */
291     @Override
292     public void navigation_() {
293         writeEndTag(HtmlMarkup.NAV);
294     }
295 
296     /** {@inheritDoc} */
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     /** {@inheritDoc} */
305     @Override
306     public void sidebar_() {
307         writeEndTag(HtmlMarkup.ASIDE);
308     }
309 
310     /** {@inheritDoc} */
311     @Override
312     public void section(int level, SinkEventAttributes attributes) {
313         onSection(level, attributes);
314     }
315 
316     /** {@inheritDoc} */
317     @Override
318     public void sectionTitle(int level, SinkEventAttributes attributes) {
319         onSectionTitle(level, attributes);
320     }
321 
322     /** {@inheritDoc} */
323     @Override
324     public void sectionTitle_(int level) {
325         onSectionTitle_(level);
326     }
327 
328     /** {@inheritDoc} */
329     @Override
330     public void section_(int level) {
331         onSection_(level);
332     }
333 
334     /**
335      * Starts a section.
336      *
337      * @param depth The level of the section.
338      * @param attributes some attributes. May be null.
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      * Ends a section.
351      *
352      * @param depth The level of the section.
353      * @see #SECTION
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      * Starts a section title.
363      *
364      * @param depth The level of the section title.
365      * @param attributes some attributes. May be null.
366      * @see #H1
367      * @see #H2
368      * @see #H3
369      * @see #H4
370      * @see #H5
371      * @see #H6
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      * Ends a section title.
393      *
394      * @param depth The level of the section title.
395      * @see #H1
396      * @see #H2
397      * @see #H3
398      * @see #H4
399      * @see #H5
400      * @see #H6
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
427     @Override
428     public void header_() {
429         writeEndTag(HtmlMarkup.HEADER);
430     }
431 
432     /** {@inheritDoc} */
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     /** {@inheritDoc} */
456     @Override
457     public void content_() {
458         try {
459             writeEndTag(contentStack.pop());
460         } catch (EmptyStackException ese) {
461             /* do nothing if the stack is empty */
462         }
463     }
464 
465     /** {@inheritDoc} */
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     /** {@inheritDoc} */
474     @Override
475     public void footer_() {
476         writeEndTag(HtmlMarkup.FOOTER);
477     }
478 
479     // -----------------------------------------------------------------------
480     //
481     // -----------------------------------------------------------------------
482 
483     /**
484      * {@inheritDoc}
485      * @see javax.swing.text.html.HTML.Tag#UL
486      */
487     @Override
488     public void list(SinkEventAttributes attributes) {
489         if (paragraphFlag) {
490             // The content of element type "p" must match
491             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
492             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
493             paragraph_();
494         }
495 
496         MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
497 
498         writeStartTag(HtmlMarkup.UL, atts);
499     }
500 
501     /**
502      * {@inheritDoc}
503      * @see javax.swing.text.html.HTML.Tag#UL
504      */
505     @Override
506     public void list_() {
507         writeEndTag(HtmlMarkup.UL);
508     }
509 
510     /**
511      * {@inheritDoc}
512      * @see javax.swing.text.html.HTML.Tag#LI
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      * {@inheritDoc}
523      * @see javax.swing.text.html.HTML.Tag#LI
524      */
525     @Override
526     public void listItem_() {
527         writeEndTag(HtmlMarkup.LI);
528     }
529 
530     /**
531      * The default list style depends on the numbering.
532      *
533      * {@inheritDoc}
534      * @see javax.swing.text.html.HTML.Tag#OL
535      */
536     @Override
537     public void numberedList(int numbering, SinkEventAttributes attributes) {
538         if (paragraphFlag) {
539             // The content of element type "p" must match
540             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
541             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
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      * {@inheritDoc}
582      * @see javax.swing.text.html.HTML.Tag#OL
583      */
584     @Override
585     public void numberedList_() {
586         writeEndTag(HtmlMarkup.OL);
587     }
588 
589     /**
590      * {@inheritDoc}
591      * @see javax.swing.text.html.HTML.Tag#LI
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      * {@inheritDoc}
602      * @see javax.swing.text.html.HTML.Tag#LI
603      */
604     @Override
605     public void numberedListItem_() {
606         writeEndTag(HtmlMarkup.LI);
607     }
608 
609     /**
610      * {@inheritDoc}
611      * @see javax.swing.text.html.HTML.Tag#DL
612      */
613     @Override
614     public void definitionList(SinkEventAttributes attributes) {
615         if (paragraphFlag) {
616             // The content of element type "p" must match
617             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
618             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
619             paragraph_();
620         }
621 
622         MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES);
623 
624         writeStartTag(HtmlMarkup.DL, atts);
625     }
626 
627     /**
628      * {@inheritDoc}
629      * @see javax.swing.text.html.HTML.Tag#DL
630      */
631     @Override
632     public void definitionList_() {
633         writeEndTag(HtmlMarkup.DL);
634     }
635 
636     /**
637      * {@inheritDoc}
638      * @see javax.swing.text.html.HTML.Tag#DT
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      * {@inheritDoc}
649      * @see javax.swing.text.html.HTML.Tag#DT
650      */
651     @Override
652     public void definedTerm_() {
653         writeEndTag(HtmlMarkup.DT);
654     }
655 
656     /**
657      * {@inheritDoc}
658      * @see javax.swing.text.html.HTML.Tag#DD
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      * {@inheritDoc}
669      * @see javax.swing.text.html.HTML.Tag#DD
670      */
671     @Override
672     public void definition_() {
673         writeEndTag(HtmlMarkup.DD);
674     }
675 
676     /** {@inheritDoc} */
677     @Override
678     public void figure(SinkEventAttributes attributes) {
679         writeStartTag(HtmlMarkup.FIGURE, attributes);
680     }
681 
682     /** {@inheritDoc} */
683     @Override
684     public void figure_() {
685         writeEndTag(HtmlMarkup.FIGURE);
686     }
687 
688     /** {@inheritDoc} */
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     /** {@inheritDoc} */
707     @Override
708     public void figureCaption(SinkEventAttributes attributes) {
709         writeStartTag(HtmlMarkup.FIGCAPTION, attributes);
710     }
711 
712     /** {@inheritDoc} */
713     @Override
714     public void figureCaption_() {
715         writeEndTag(HtmlMarkup.FIGCAPTION);
716     }
717 
718     /**
719      * {@inheritDoc}
720      * @see javax.swing.text.html.HTML.Tag#P
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      * {@inheritDoc}
733      * @see javax.swing.text.html.HTML.Tag#P
734      */
735     @Override
736     public void paragraph_() {
737         if (paragraphFlag) {
738             writeEndTag(HtmlMarkup.P);
739             paragraphFlag = false;
740         }
741     }
742 
743     /** {@inheritDoc} */
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     /** {@inheritDoc} */
758     @Override
759     public void data_() {
760         writeEndTag(HtmlMarkup.DATA);
761     }
762 
763     /** {@inheritDoc} */
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     /** {@inheritDoc} */
778     @Override
779     public void time_() {
780         writeEndTag(HtmlMarkup.TIME);
781     }
782 
783     /**
784      * {@inheritDoc}
785      * @see javax.swing.text.html.HTML.Tag#ADDRESS
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      * {@inheritDoc}
796      * @see javax.swing.text.html.HTML.Tag#ADDRESS
797      */
798     @Override
799     public void address_() {
800         writeEndTag(HtmlMarkup.ADDRESS);
801     }
802 
803     /**
804      * {@inheritDoc}
805      * @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE
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      * {@inheritDoc}
816      * @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE
817      */
818     @Override
819     public void blockquote_() {
820         writeEndTag(HtmlMarkup.BLOCKQUOTE);
821     }
822 
823     /**
824      * {@inheritDoc}
825      * @see javax.swing.text.html.HTML.Tag#DIV
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      * {@inheritDoc}
836      * @see javax.swing.text.html.HTML.Tag#DIV
837      */
838     @Override
839     public void division_() {
840         writeEndTag(HtmlMarkup.DIV);
841     }
842 
843     /**
844      * Depending on whether the decoration attribute is "source" or not, this leads
845      * to either emitting {@code <pre><code>} or just {@code <pre>}.
846      * No default classes are emitted but the given attributes are always added to the {@code pre} element only.
847      *
848      * {@inheritDoc}
849      * @see javax.swing.text.html.HTML.Tag#PRE
850      * @see javax.swing.text.html.HTML.Tag#CODE
851      */
852     @Override
853     public void verbatim(SinkEventAttributes attributes) {
854         if (paragraphFlag) {
855             // The content of element type "p" must match
856             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
857             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
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      * {@inheritDoc}
885      * @see javax.swing.text.html.HTML.Tag#CODE
886      * @see javax.swing.text.html.HTML.Tag#PRE
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      * {@inheritDoc}
900      * @see javax.swing.text.html.HTML.Tag#HR
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     /** {@inheritDoc} */
910     @Override
911     public void table(SinkEventAttributes attributes) {
912         this.tableContentWriterStack.addLast(new StringWriter());
913 
914         if (paragraphFlag) {
915             // The content of element type "p" must match
916             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
917             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
918             paragraph_();
919         }
920 
921         // start table with tableRows
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      * {@inheritDoc}
931      * @see javax.swing.text.html.HTML.Tag#TABLE
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             // DOXIA-177
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      * The default style class is <code>bodyTable</code>.
968      *
969      * @param grid if {@code true} the style class {@code bodyTableBorder} will be added
970      *
971      * {@inheritDoc}
972      * @see javax.swing.text.html.HTML.Tag#TABLE
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     /** {@inheritDoc} */
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      * Rows are striped with two colors by adding the class <code>a</code> or <code>b</code>. If the provided attributes
1013      * specify the <code>hidden</code> class, the next call to tableRow will set the same striping class as this one. A
1014      * style for <code>hidden</code> or <code>table.bodyTable hidden</code> may need to be provided to actually hide
1015      * such a row. {@inheritDoc}
1016      *
1017      * @see javax.swing.text.html.HTML.Tag#TR
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      * {@inheritDoc}
1053      * @see javax.swing.text.html.HTML.Tag#TR
1054      */
1055     @Override
1056     public void tableRow_() {
1057         writeEndTag(HtmlMarkup.TR);
1058     }
1059 
1060     /** {@inheritDoc} */
1061     @Override
1062     public void tableCell(SinkEventAttributes attributes) {
1063         tableCell(false, attributes);
1064     }
1065 
1066     /** {@inheritDoc} */
1067     @Override
1068     public void tableHeaderCell(SinkEventAttributes attributes) {
1069         tableCell(true, attributes);
1070     }
1071 
1072     /**
1073      * @param headerRow true if it is an header row
1074      * @param attributes the cell attributes
1075      * @see javax.swing.text.html.HTML.Tag#TH
1076      * @see javax.swing.text.html.HTML.Tag#TD
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     /** {@inheritDoc} */
1116     @Override
1117     public void tableCell_() {
1118         tableCell_(false);
1119     }
1120 
1121     /** {@inheritDoc} */
1122     @Override
1123     public void tableHeaderCell_() {
1124         tableCell_(true);
1125     }
1126 
1127     /**
1128      * Ends a table cell.
1129      *
1130      * @param headerRow true if it is an header row
1131      * @see javax.swing.text.html.HTML.Tag#TH
1132      * @see javax.swing.text.html.HTML.Tag#TD
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      * {@inheritDoc}
1149      * @see javax.swing.text.html.HTML.Tag#CAPTION
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         // TODO: tableCaption should be written before tableRows (DOXIA-177)
1158         MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES);
1159 
1160         writeStartTag(HtmlMarkup.CAPTION, atts);
1161     }
1162 
1163     /**
1164      * {@inheritDoc}
1165      * @see javax.swing.text.html.HTML.Tag#CAPTION
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      * {@inheritDoc}
1180      * @see javax.swing.text.html.HTML.Tag#A
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      * {@inheritDoc}
1209      * @see javax.swing.text.html.HTML.Tag#A
1210      */
1211     @Override
1212     public void anchor_() {
1213         if (!headFlag) {
1214             writeEndTag(HtmlMarkup.A);
1215         }
1216     }
1217 
1218     /**
1219      * The default style class for external link is <code>externalLink</code>.
1220      *
1221      * {@inheritDoc}
1222      * @see javax.swing.text.html.HTML.Tag#A
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      * {@inheritDoc}
1255      * @see javax.swing.text.html.HTML.Tag#A
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
1315     @Override
1316     public void inline_() {
1317         if (!headFlag) {
1318             for (Tag tag : inlineStack.pop()) {
1319                 writeEndTag(tag);
1320             }
1321         }
1322     }
1323 
1324     /**
1325      * {@inheritDoc}
1326      * @see javax.swing.text.html.HTML.Tag#I
1327      */
1328     @Override
1329     public void italic() {
1330         inline(SinkEventAttributeSet.Semantics.ITALIC);
1331     }
1332 
1333     /**
1334      * {@inheritDoc}
1335      * @see javax.swing.text.html.HTML.Tag#I
1336      */
1337     @Override
1338     public void italic_() {
1339         inline_();
1340     }
1341 
1342     /**
1343      * {@inheritDoc}
1344      * @see javax.swing.text.html.HTML.Tag#B
1345      */
1346     @Override
1347     public void bold() {
1348         inline(SinkEventAttributeSet.Semantics.BOLD);
1349     }
1350 
1351     /**
1352      * {@inheritDoc}
1353      * @see javax.swing.text.html.HTML.Tag#B
1354      */
1355     @Override
1356     public void bold_() {
1357         inline_();
1358     }
1359 
1360     /**
1361      * {@inheritDoc}
1362      * @see javax.swing.text.html.HTML.Tag#CODE
1363      */
1364     @Override
1365     public void monospaced() {
1366         inline(SinkEventAttributeSet.Semantics.CODE);
1367     }
1368 
1369     /**
1370      * {@inheritDoc}
1371      * @see javax.swing.text.html.HTML.Tag#CODE
1372      */
1373     @Override
1374     public void monospaced_() {
1375         inline_();
1376     }
1377 
1378     /**
1379      * {@inheritDoc}
1380      * @see javax.swing.text.html.HTML.Tag#BR
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
1404     @Override
1405     public void pageBreak() {
1406         comment(" PB ");
1407     }
1408 
1409     /** {@inheritDoc} */
1410     @Override
1411     public void nonBreakingSpace() {
1412         if (headFlag) {
1413             getTextBuffer().append(' ');
1414         } else {
1415             write("&#160;");
1416         }
1417     }
1418 
1419     /** {@inheritDoc} */
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     /** {@inheritDoc} */
1438     @Override
1439     public void rawText(String text) {
1440         if (headFlag) {
1441             getTextBuffer().append(text);
1442         } else {
1443             write(text);
1444         }
1445     }
1446 
1447     /** {@inheritDoc} */
1448     @Override
1449     public void comment(String comment) {
1450         if (comment != null) {
1451             final String originalComment = comment;
1452 
1453             // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments
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      * {@inheritDoc}
1479      *
1480      * Add an unknown event.
1481      * This can be used to generate html tags for which no corresponding sink event exists.
1482      *
1483      * <p>
1484      * If {@link org.apache.maven.doxia.util.HtmlTools#getHtmlTag(String) HtmlTools.getHtmlTag(name)}
1485      * does not return null, the corresponding tag will be written.
1486      * </p>
1487      *
1488      * <p>For example, the div block</p>
1489      *
1490      * <pre>
1491      *  &lt;div class="detail" style="display:inline"&gt;text&lt;/div&gt;
1492      * </pre>
1493      *
1494      * <p>can be generated via the following event sequence:</p>
1495      *
1496      * <pre>
1497      *  SinkEventAttributeSet atts = new SinkEventAttributeSet();
1498      *  atts.addAttribute(SinkEventAttributes.CLASS, "detail");
1499      *  atts.addAttribute(SinkEventAttributes.STYLE, "display:inline");
1500      *  sink.unknown("div", new Object[]{new Integer(HtmlMarkup.TAG_TYPE_START)}, atts);
1501      *  sink.text("text");
1502      *  sink.unknown("div", new Object[]{new Integer(HtmlMarkup.TAG_TYPE_END)}, null);
1503      * </pre>
1504      *
1505      * @param name the name of the event. If this is not a valid xhtml tag name
1506      *      as defined in {@link org.apache.maven.doxia.markup.HtmlMarkup} then the event is ignored.
1507      * @param requiredParams If this is null or the first argument is not an Integer then the event is ignored.
1508      *      The first argument should indicate the type of the unknown event, its integer value should be one of
1509      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_START TAG_TYPE_START},
1510      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_END TAG_TYPE_END},
1511      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_SIMPLE TAG_TYPE_SIMPLE},
1512      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#ENTITY_TYPE ENTITY_TYPE}, or
1513      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#CDATA_TYPE CDATA_TYPE},
1514      *      otherwise the event will be ignored.
1515      * @param attributes a set of attributes for the event. May be null.
1516      *      The attributes will always be written, no validity check is performed.
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     /** {@inheritDoc} */
1572     @Override
1573     public void flush() {
1574         writer.flush();
1575     }
1576 
1577     /** {@inheritDoc} */
1578     @Override
1579     public void close() {
1580         writer.close();
1581 
1582         init();
1583     }
1584 
1585     // ----------------------------------------------------------------------
1586     //
1587     // ----------------------------------------------------------------------
1588 
1589     /**
1590      * Write HTML escaped text to output.
1591      *
1592      * @param text The text to write.
1593      */
1594     protected void content(String text) {
1595         // small hack due to DOXIA-314
1596         String txt = escapeHTML(text);
1597         txt = StringUtils.replace(txt, "&amp;#", "&#");
1598         write(txt);
1599     }
1600 
1601     /**
1602      * Write HTML escaped text to output.
1603      *
1604      * @param text The text to write.
1605      */
1606     protected void verbatimContent(String text) {
1607         write(escapeHTML(text));
1608     }
1609 
1610     /**
1611      * Forward to HtmlTools.escapeHTML(text).
1612      *
1613      * @param text the String to escape, may be null
1614      * @return the text escaped, "" if null String input
1615      * @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String)
1616      */
1617     protected static String escapeHTML(String text) {
1618         return HtmlTools.escapeHTML(text, false);
1619     }
1620 
1621     /**
1622      * Forward to HtmlTools.encodeURL(text).
1623      *
1624      * @param text the String to encode, may be null.
1625      * @return the text encoded, null if null String input.
1626      * @see org.apache.maven.doxia.util.HtmlTools#encodeURL(String)
1627      */
1628     protected static String encodeURL(String text) {
1629         return HtmlTools.encodeURL(text);
1630     }
1631 
1632     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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 }