001package org.apache.maven.doxia.module.confluence;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.PrintWriter;
023import java.io.StringWriter;
024import java.io.Writer;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Stack;
028
029import javax.swing.text.html.HTML.Attribute;
030
031import org.apache.maven.doxia.sink.SinkEventAttributes;
032import org.apache.maven.doxia.sink.impl.AbstractTextSink;
033import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
034import org.apache.maven.doxia.util.HtmlTools;
035import org.codehaus.plexus.util.StringUtils;
036
037/**
038 * Confluence Sink implementation.
039 * <br>
040 * <b>Note</b>: The encoding used is UTF-8.
041 *
042 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
043 * @since 1.0
044 */
045public class ConfluenceSink
046    extends AbstractTextSink
047    implements ConfluenceMarkup
048{
049    /**  The writer to use. */
050    private final PrintWriter out;
051
052    /**  The writer to use. */
053    private StringWriter writer;
054
055    /** An indication on if we're in head mode. */
056    private boolean headFlag;
057
058    private int levelList = 0;
059
060    /**  listStyles. */
061    private final Stack<String> listStyles;
062
063    /** An indication on if we're in monospaced mode. */
064    private boolean monospacedFlag;
065
066    /** Keep track of the closing tags for inline events. */
067    protected Stack<List<String>> inlineStack = new Stack<>();
068
069    /** An indication on if we're in verbatim box mode. */
070    private boolean verbatimBoxedFlag;
071
072    /** An indication on if we're in table mode. */
073    private boolean tableFlag;
074
075    /** An indication on if we're in table header mode. */
076    private boolean tableHeaderFlag;
077
078    /** The link name. */
079    private String linkName;
080
081    /**
082     * Constructor, initialize the Writer and the variables.
083     *
084     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
085     * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
086     */
087    protected ConfluenceSink( Writer writer )
088    {
089        this.out = new PrintWriter( writer );
090        this.listStyles = new Stack<>();
091
092        init();
093    }
094
095    /** {@inheritDoc} */
096    public void anchor( String name )
097    {
098        write( ANCHOR_START_MARKUP + name + ANCHOR_END_MARKUP );
099    }
100
101    /** {@inheritDoc} */
102    public void anchor( String name, SinkEventAttributes attributes )
103    {
104        anchor( name );
105    }
106
107    /**
108     * Not used.
109     * {@inheritDoc}
110     */
111    public void anchor_()
112    {
113        // nop
114    }
115
116    /**
117     * Not used.
118     * {@inheritDoc}
119     */
120    public void author()
121    {
122        // nop
123    }
124
125    /** {@inheritDoc} */
126    public void author( SinkEventAttributes attributes )
127    {
128        author();
129    }
130
131    /**
132     * Not used.
133     * {@inheritDoc}
134     */
135    public void author_()
136    {
137        // nop
138    }
139
140    /**
141     * Not used.
142     * {@inheritDoc}
143     */
144    public void body()
145    {
146        // nop
147    }
148
149    /** {@inheritDoc} */
150    public void body( SinkEventAttributes attributes )
151    {
152        body();
153    }
154
155    /**
156     * Not used.
157     * {@inheritDoc}
158     */
159    public void body_()
160    {
161        // nop
162    }
163
164    /**
165     * {@inheritDoc}
166     */
167    public void bold()
168    {
169        inline( SinkEventAttributeSet.Semantics.BOLD );
170    }
171
172    /**
173     * {@inheritDoc}
174     */
175    public void bold_()
176    {
177        inline_();
178    }
179
180    /**
181     * Not used.
182     * {@inheritDoc}
183     */
184    public void comment( String comment )
185    {
186        // nop
187    }
188
189    /**
190     * {@inheritDoc}
191     */
192    public void close()
193    {
194        out.write( writer.toString() );
195        out.close();
196
197        init();
198    }
199
200    /**
201     * Not used.
202     * {@inheritDoc}
203     */
204    public void date()
205    {
206        // nop
207    }
208
209    /** {@inheritDoc} */
210    public void date( SinkEventAttributes attributes )
211    {
212        date();
213    }
214
215    /**
216     * Not used.
217     * {@inheritDoc}
218     */
219    public void date_()
220    {
221        // nop
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    public void definedTerm()
228    {
229        // nop
230    }
231
232    /** {@inheritDoc} */
233    public void definedTerm( SinkEventAttributes attributes )
234    {
235        definedTerm();
236    }
237
238    /**
239     * {@inheritDoc}
240     */
241    public void definedTerm_()
242    {
243        writeEOL( true );
244    }
245
246    /**
247     * {@inheritDoc}
248     */
249    public void definition()
250    {
251        writer.write( CITATION_START_MARKUP );
252    }
253
254    /** {@inheritDoc} */
255    public void definition( SinkEventAttributes attributes )
256    {
257        definition();
258    }
259
260    /**
261     * {@inheritDoc}
262     */
263    public void definition_()
264    {
265        writer.write( CITATION_END_MARKUP );
266        writeEOL( true );
267    }
268
269    /**
270     * Not used.
271     * {@inheritDoc}
272     */
273    public void definitionList()
274    {
275        // nop
276    }
277
278    /**
279     * Not used.
280     * {@inheritDoc}
281     */
282    public void definitionList( SinkEventAttributes attributes )
283    {
284        // nop
285    }
286
287    /**
288     * {@inheritDoc}
289     */
290    public void definitionList_()
291    {
292        writeEOL();
293    }
294
295    /**
296     * Not used.
297     * {@inheritDoc}
298     */
299    public void definitionListItem()
300    {
301        // nop
302    }
303
304    /**
305     * Not used.
306     * {@inheritDoc}
307     */
308    public void definitionListItem( SinkEventAttributes attributes )
309    {
310        // nop
311    }
312
313    /**
314     * Not used.
315     * {@inheritDoc}
316     */
317    public void definitionListItem_()
318    {
319        // nop
320    }
321
322    /**
323     * Not used.
324     * {@inheritDoc}
325     */
326    public void figure()
327    {
328        // nop
329    }
330
331    /** {@inheritDoc} */
332    public void figure( SinkEventAttributes attributes )
333    {
334        figure();
335    }
336
337    /**
338     * Not used.
339     * {@inheritDoc}
340     */
341    public void figure_()
342    {
343        // nop
344    }
345
346    /**
347     * Not used.
348     * {@inheritDoc}
349     */
350    public void figureCaption()
351    {
352        // nop
353    }
354
355    /** {@inheritDoc} */
356    public void figureCaption( SinkEventAttributes attributes )
357    {
358        figureCaption();
359    }
360
361    /**
362     * Not used.
363     * {@inheritDoc}
364     */
365    public void figureCaption_()
366    {
367        // nop;
368    }
369
370    /** {@inheritDoc} */
371    public void figureGraphics( String name )
372    {
373        writeEOL();
374        write( FIGURE_START_MARKUP + name + FIGURE_END_MARKUP );
375    }
376
377    /** {@inheritDoc} */
378    public void figureGraphics( String src, SinkEventAttributes attributes )
379    {
380        figureGraphics( src );
381        if ( attributes != null && attributes.getAttribute( Attribute.ALT.toString() ) != null )
382        {
383            write( attributes.getAttribute( Attribute.ALT.toString() ).toString() );
384            writeEOL( true );
385        }
386    }
387
388    /**
389     * {@inheritDoc}
390     */
391    public void flush()
392    {
393        close();
394        writer.flush();
395    }
396
397    /**
398     * {@inheritDoc}
399     */
400    public void head()
401    {
402        init();
403
404        headFlag = true;
405    }
406
407    /** {@inheritDoc} */
408    public void head( SinkEventAttributes attributes )
409    {
410        head();
411    }
412
413    /**
414     * {@inheritDoc}
415     */
416    public void head_()
417    {
418        headFlag = false;
419    }
420
421    /**
422     * Not used.
423     * {@inheritDoc}
424     */
425    public void horizontalRule()
426    {
427        // nop
428    }
429
430    /** {@inheritDoc} */
431    public void horizontalRule( SinkEventAttributes attributes )
432    {
433        horizontalRule();
434    }
435
436    /**
437     * {@inheritDoc}
438     */
439    public void inline()
440    {
441        inline( null );
442    }
443
444    /** {@inheritDoc} */
445    public void inline( SinkEventAttributes attributes )
446    {
447        if ( !headFlag )
448        {
449            List<String> tags = new ArrayList<>();
450
451            if ( attributes != null )
452            {
453
454                if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) )
455                {
456                    write( ITALIC_START_MARKUP );
457                    tags.add( 0, ITALIC_END_MARKUP );
458                }
459
460                if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) )
461                {
462                    write( BOLD_START_MARKUP );
463                    tags.add( 0, BOLD_END_MARKUP );
464                }
465
466                if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) )
467                {
468                    write( MONOSPACED_START_MARKUP );
469                    tags.add( 0, MONOSPACED_END_MARKUP );
470                }
471
472            }
473
474            inlineStack.push( tags );
475        }
476    }
477
478    /**
479     * {@inheritDoc}
480     */
481    public void inline_()
482    {
483        if ( !headFlag )
484        {
485            for ( String tag: inlineStack.pop() )
486            {
487                write( tag );
488            }
489        }
490    }
491
492    /**
493     * {@inheritDoc}
494     */
495    public void italic()
496    {
497        inline( SinkEventAttributeSet.Semantics.ITALIC );
498    }
499
500    /**
501     * {@inheritDoc}
502     */
503    public void italic_()
504    {
505        inline_();
506    }
507
508    /**
509     * {@inheritDoc}
510     */
511    public void lineBreak()
512    {
513        write( LINE_BREAK_MARKUP );
514        writeEOL();
515    }
516
517    /** {@inheritDoc} */
518    public void lineBreak( SinkEventAttributes attributes )
519    {
520        lineBreak();
521    }
522
523    /** {@inheritDoc} */
524    public void link( String name )
525    {
526        linkName = name;
527    }
528
529    /** {@inheritDoc} */
530    public void link( String name, SinkEventAttributes attributes )
531    {
532        link( name );
533    }
534
535    /**
536     * {@inheritDoc}
537     */
538    public void link_()
539    {
540        linkName = null;
541        write( LINK_END_MARKUP );
542    }
543
544    /**
545     * {@inheritDoc}
546     */
547    public void list()
548    {
549        if ( !writer.toString().endsWith( EOL + EOL ) )
550        {
551            writeEOL( true );
552        }
553
554        levelList++;
555    }
556
557    /** {@inheritDoc} */
558    public void list( SinkEventAttributes attributes )
559    {
560        list();
561    }
562
563    /**
564     * {@inheritDoc}
565     */
566    public void list_()
567    {
568        levelList--;
569        if ( levelList == 0 )
570        {
571            writeEOL( true );
572            writeEOL();
573        }
574    }
575
576    /**
577     * {@inheritDoc}
578     */
579    public void listItem()
580    {
581        write( StringUtils.repeat( "*", levelList ) + " " );
582    }
583
584    /** {@inheritDoc} */
585    public void listItem( SinkEventAttributes attributes )
586    {
587        listItem();
588    }
589
590    /**
591     * {@inheritDoc}
592     */
593    public void listItem_()
594    {
595        writeEOL( true );
596    }
597
598    /**
599     * {@inheritDoc}
600     */
601    public void monospaced()
602    {
603        monospacedFlag = true;
604        inline( SinkEventAttributeSet.Semantics.CODE );
605    }
606
607    /**
608     * {@inheritDoc}
609     */
610    public void monospaced_()
611    {
612        monospacedFlag = false;
613        inline_();
614    }
615
616    /**
617     * Not used.
618     * {@inheritDoc}
619     */
620    public void nonBreakingSpace()
621    {
622        // nop
623    }
624
625    /** {@inheritDoc} */
626    public void numberedList( int numbering )
627    {
628        if ( !writer.toString().endsWith( EOL + EOL ) )
629        {
630            writeEOL( true );
631        }
632        levelList++;
633
634        String style;
635        switch ( numbering )
636        {
637            case NUMBERING_UPPER_ALPHA:
638            case NUMBERING_LOWER_ALPHA:
639            case NUMBERING_UPPER_ROMAN:
640            case NUMBERING_LOWER_ROMAN:
641            case NUMBERING_DECIMAL:
642            default:
643                style = NUMBERING_MARKUP;
644        }
645
646        listStyles.push( style );
647    }
648
649    /** {@inheritDoc} */
650    public void numberedList( int numbering, SinkEventAttributes attributes )
651    {
652        numberedList( numbering );
653    }
654
655    /**
656     * {@inheritDoc}
657     */
658    public void numberedList_()
659    {
660        levelList--;
661        if ( levelList == 0 )
662        {
663            writeEOL( true );
664            writeEOL();
665        }
666        listStyles.pop();
667    }
668
669    /**
670     * {@inheritDoc}
671     */
672    public void numberedListItem()
673    {
674        writeEOL( true );
675        String style = listStyles.peek();
676        // We currently only handle one type of numbering style for Confluence,
677        // so we can just repeat the latest numbering markup for each level.
678        // If we ever decide to handle multiple different numbering styles, we'd
679        // need to traverse the entire listStyles stack and use the correct
680        // numbering style for each level.
681        write( StringUtils.repeat( style, levelList ) + SPACE );
682    }
683
684    /** {@inheritDoc} */
685    public void numberedListItem( SinkEventAttributes attributes )
686    {
687        numberedListItem();
688    }
689
690    /**
691     * {@inheritDoc}
692     */
693    public void numberedListItem_()
694    {
695        writeEOL( true );
696    }
697
698    /**
699     * Not used.
700     * {@inheritDoc}
701     */
702    public void pageBreak()
703    {
704        // nop
705    }
706
707    /**
708     * Not used.
709     * {@inheritDoc}
710     */
711    public void paragraph()
712    {
713        // nop
714    }
715
716    /** {@inheritDoc} */
717    public void paragraph( SinkEventAttributes attributes )
718    {
719        paragraph();
720    }
721
722    /**
723     * {@inheritDoc}
724     */
725    public void paragraph_()
726    {
727        writeEOL( true );
728        writeEOL();
729    }
730
731    /**
732     * Not used.
733     * {@inheritDoc}
734     */
735    public void rawText( String text )
736    {
737        // nop
738    }
739
740    /**
741     * Not used.
742     * {@inheritDoc}
743     */
744    public void section( int level, SinkEventAttributes attributes )
745    {
746        // nop
747    }
748
749    /**
750     * Not used.
751     * {@inheritDoc}
752     */
753    public void section1()
754    {
755        // nop
756    }
757
758    /**
759     * Not used.
760     * {@inheritDoc}
761     */
762    public void section1_()
763    {
764        // nop
765    }
766
767    /**
768     * Not used.
769     * {@inheritDoc}
770     */
771    public void section2()
772    {
773        // nop
774    }
775
776    /**
777     * Not used.
778     * {@inheritDoc}
779     */
780    public void section2_()
781    {
782        // nop
783    }
784
785    /**
786     * Not used.
787     * {@inheritDoc}
788     */
789    public void section3()
790    {
791        // nop
792    }
793
794    /**
795     * Not used.
796     * {@inheritDoc}
797     */
798    public void section3_()
799    {
800        // nop
801    }
802
803    /**
804     * Not used.
805     * {@inheritDoc}
806     */
807    public void section4()
808    {
809        // nop
810    }
811
812    /**
813     * Not used.
814     * {@inheritDoc}
815     */
816    public void section4_()
817    {
818        // nop
819    }
820
821    /**
822     * Not used.
823     * {@inheritDoc}
824     */
825    public void section5()
826    {
827        // nop
828    }
829
830    /**
831     * Not used.
832     * {@inheritDoc}
833     */
834    public void section5_()
835    {
836        // nop
837    }
838
839    /**
840     * Not used.
841     * {@inheritDoc}
842     */
843    public void section_( int level )
844    {
845        // nop
846    }
847
848    /**
849     * Not used.
850     * {@inheritDoc}
851     */
852    public void sectionTitle()
853    {
854        // nop
855    }
856
857    /** {@inheritDoc} */
858    public void sectionTitle( int level, SinkEventAttributes attributes )
859    {
860        if ( level > 0 && level < 6 )
861        {
862            write( "h" + level + ". " );
863        }
864    }
865
866    /**
867     * {@inheritDoc}
868     */
869    public void sectionTitle1()
870    {
871        sectionTitle( 1, null );
872    }
873
874    /**
875     * {@inheritDoc}
876     */
877    public void sectionTitle1_()
878    {
879        sectionTitle_( 1 );
880    }
881
882    /**
883     * {@inheritDoc}
884     */
885    public void sectionTitle2()
886    {
887        sectionTitle( 2, null );
888    }
889
890    /**
891     * {@inheritDoc}
892     */
893    public void sectionTitle2_()
894    {
895        sectionTitle_( 2 );
896    }
897
898    /**
899     * {@inheritDoc}
900     */
901    public void sectionTitle3()
902    {
903        sectionTitle( 3, null );
904    }
905
906    /**
907     * {@inheritDoc}
908     */
909    public void sectionTitle3_()
910    {
911        sectionTitle_( 3 );
912    }
913
914    /**
915     * {@inheritDoc}
916     */
917    public void sectionTitle4()
918    {
919        sectionTitle( 4, null );
920    }
921
922    /**
923     * {@inheritDoc}
924     */
925    public void sectionTitle4_()
926    {
927        sectionTitle_( 4 );
928    }
929
930    /**
931     * {@inheritDoc}
932     */
933    public void sectionTitle5()
934    {
935        sectionTitle( 5, null );
936    }
937
938    /**
939     * {@inheritDoc}
940     */
941    public void sectionTitle5_()
942    {
943        sectionTitle_( 5 );
944    }
945
946    /**
947     * Not used.
948     * {@inheritDoc}
949     */
950    public void sectionTitle_()
951    {
952        // nop
953    }
954
955    /** {@inheritDoc} */
956    public void sectionTitle_( int level )
957    {
958        writeEOL( true );
959        writeEOL();
960    }
961
962    /**
963     * {@inheritDoc}
964     */
965    public void table()
966    {
967        // nop
968        tableFlag = true;
969        writeEOL( true );
970        writeEOL();
971    }
972
973    /** {@inheritDoc} */
974    public void table( SinkEventAttributes attributes )
975    {
976        table();
977    }
978
979    /**
980     * {@inheritDoc}
981     */
982    public void table_()
983    {
984        tableFlag = false;
985        writeEOL( true );
986        writeEOL();
987    }
988
989    /**
990     * Not used.
991     * {@inheritDoc}
992     */
993    public void tableCaption()
994    {
995        // nop
996    }
997
998    /** {@inheritDoc} */
999    public void tableCaption( SinkEventAttributes attributes )
1000    {
1001        tableCaption();
1002    }
1003
1004    /**
1005     * Not used.
1006     * {@inheritDoc}
1007     */
1008    public void tableCaption_()
1009    {
1010        // nop
1011    }
1012
1013    /**
1014     * {@inheritDoc}
1015     */
1016    public void tableCell()
1017    {
1018        write( " " );
1019    }
1020
1021    /** {@inheritDoc} */
1022    public void tableCell( SinkEventAttributes attributes )
1023    {
1024        tableCell();
1025    }
1026
1027    /** {@inheritDoc} */
1028    public void tableCell( String width )
1029    {
1030        tableCell();
1031    }
1032
1033    /**
1034     * {@inheritDoc}
1035     */
1036    public void tableCell_()
1037    {
1038        write( " " );
1039        write( TABLE_CELL_MARKUP );
1040    }
1041
1042    /**
1043     * {@inheritDoc}
1044     */
1045    public void tableHeaderCell()
1046    {
1047        tableHeaderFlag = true;
1048        write( TABLE_CELL_HEADER_START_MARKUP );
1049    }
1050
1051    /** {@inheritDoc} */
1052    public void tableHeaderCell( SinkEventAttributes attributes )
1053    {
1054        tableHeaderCell();
1055    }
1056
1057    /** {@inheritDoc} */
1058    public void tableHeaderCell( String width )
1059    {
1060        tableHeaderCell();
1061    }
1062
1063    /**
1064     * {@inheritDoc}
1065     */
1066    public void tableHeaderCell_()
1067    {
1068        write( TABLE_CELL_HEADER_END_MARKUP );
1069    }
1070
1071    /**
1072     * {@inheritDoc}
1073     */
1074    public void tableRow()
1075    {
1076        write( TABLE_ROW_MARKUP );
1077    }
1078
1079    /** {@inheritDoc} */
1080    public void tableRow( SinkEventAttributes attributes )
1081    {
1082        tableRow();
1083    }
1084
1085    /**
1086     * {@inheritDoc}
1087     */
1088    public void tableRow_()
1089    {
1090        if ( tableHeaderFlag )
1091        {
1092            tableHeaderFlag = false;
1093            write( TABLE_ROW_MARKUP );
1094        }
1095        writeEOL( true );
1096    }
1097
1098    /**
1099     * Not used.
1100     * {@inheritDoc}
1101     */
1102    public void tableRows( int[] justification, boolean grid )
1103    {
1104        // nop
1105    }
1106
1107    /**
1108     * Not used.
1109     * {@inheritDoc}
1110     */
1111    public void tableRows_()
1112    {
1113        // nop
1114    }
1115
1116    /** {@inheritDoc} */
1117    public void text( String text )
1118    {
1119        if ( headFlag )
1120        {
1121            return;
1122        }
1123
1124        if ( linkName != null )
1125        {
1126            write( LINK_START_MARKUP );
1127        }
1128
1129        if ( tableFlag )
1130        {
1131            // Remove line breaks, because it interferes with the table syntax
1132            String strippedText = StringUtils.replace( text, "\n", "" );
1133            // Trim if only whitespace, to handle ignorable whitespace from xdoc documents
1134            if ( StringUtils.isWhitespace( strippedText ) )
1135            {
1136                strippedText = StringUtils.trim( strippedText );
1137            }
1138            content( strippedText );
1139        }
1140        else if ( monospacedFlag )
1141        {
1142            content( text, true );
1143        }
1144        else
1145        {
1146            content( text );
1147        }
1148
1149        if ( linkName != null )
1150        {
1151            write( LINK_MIDDLE_MARKUP + linkName );
1152        }
1153    }
1154
1155    /** {@inheritDoc} */
1156    public void text( String text, SinkEventAttributes attributes )
1157    {
1158        if ( attributes == null )
1159        {
1160            text( text );
1161        }
1162        else
1163        {
1164            if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "underline" ) )
1165            {
1166                write( UNDERLINED_START_MARKUP );
1167            }
1168            else if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "line-through" ) )
1169            {
1170                write( STRIKETHROUGH_START_MARKUP );
1171            }
1172            if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sub" ) )
1173            {
1174                write( SUBSCRIPT_START_MARKUP );
1175            }
1176            else if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sup" ) )
1177            {
1178                write( SUPERSCRIPT_START_MARKUP );
1179            }
1180
1181            text( text );
1182
1183            if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sup" ) )
1184            {
1185                write( SUPERSCRIPT_END_MARKUP );
1186            }
1187            else if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sub" ) )
1188            {
1189                write( SUBSCRIPT_END_MARKUP );
1190            }
1191            if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "line-through" ) )
1192            {
1193                write( STRIKETHROUGH_END_MARKUP );
1194            }
1195            else if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "underline" ) )
1196            {
1197                write( UNDERLINED_END_MARKUP );
1198            }
1199        }
1200    }
1201
1202    /**
1203     * Not used.
1204     * {@inheritDoc}
1205     */
1206    public void title()
1207    {
1208        // nop
1209    }
1210
1211    /** {@inheritDoc} */
1212    public void title( SinkEventAttributes attributes )
1213    {
1214        title();
1215    }
1216
1217    /**
1218     * Not used.
1219     * {@inheritDoc}
1220     */
1221    public void title_()
1222    {
1223        // nop
1224    }
1225
1226    /**
1227     * Not used.
1228     * {@inheritDoc}
1229     */
1230    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1231    {
1232        // nop
1233    }
1234
1235    /** {@inheritDoc} */
1236    public void verbatim( boolean boxed )
1237    {
1238        if ( boxed )
1239        {
1240            verbatimBoxedFlag = true;
1241        }
1242
1243        if ( verbatimBoxedFlag )
1244        {
1245            write( "{code:borderStyle=solid}" );
1246        }
1247        else
1248        {
1249            write( "{noformat}" );
1250        }
1251        writeEOL( true );
1252    }
1253
1254    /**
1255     * {@inheritDoc}
1256     */
1257    public void verbatim_()
1258    {
1259        if ( verbatimBoxedFlag )
1260        {
1261            write( "{code}" );
1262        }
1263        else
1264        {
1265            write( "{noformat}" );
1266        }
1267
1268        writeEOL( true );
1269        writeEOL();
1270    }
1271
1272    // ----------------------------------------------------------------------
1273    // Private methods
1274    // ----------------------------------------------------------------------
1275
1276    private void write( String text )
1277    {
1278        writer.write( unifyEOLs( text ) );
1279    }
1280
1281    /**
1282     * Writes a system EOL.
1283     */
1284    private void writeEOL()
1285    {
1286        write( EOL );
1287    }
1288
1289    /**
1290     * Writes a system EOL, with or without trim.
1291     */
1292    private void writeEOL( boolean trim )
1293    {
1294        if ( !trim )
1295        {
1296            writeEOL();
1297            return;
1298        }
1299
1300        String tmp = writer.toString().trim();
1301        writer = new StringWriter();
1302        writer.write( tmp );
1303        write( EOL );
1304    }
1305
1306    /**
1307     * Write HTML escaped text to output.
1308     *
1309     * @param text The text to write.
1310     */
1311    protected void content( String text )
1312    {
1313        write( escapeHTML( text ) );
1314    }
1315
1316    /**
1317     * Write HTML, and optionally Confluence, escaped text to output.
1318     *
1319     * @param text The text to write.
1320     * @param escapeConfluence a boolean.
1321     */
1322    protected void content( String text, boolean escapeConfluence )
1323    {
1324        if ( escapeConfluence )
1325        {
1326            write( escapeConfluence( escapeHTML( text ) ) );
1327        }
1328        else
1329        {
1330            content( text );
1331        }
1332    }
1333
1334    /**
1335     * {@inheritDoc}
1336     */
1337    protected void init()
1338    {
1339        super.init();
1340
1341        this.writer = new StringWriter();
1342        this.monospacedFlag = false;
1343        this.headFlag = false;
1344        this.levelList = 0;
1345        this.listStyles.clear();
1346        this.verbatimBoxedFlag = false;
1347        this.tableHeaderFlag = false;
1348        this.linkName = null;
1349    }
1350
1351    /**
1352     * Escape characters that have special meaning in Confluence.
1353     *
1354     * @param text the String to escape, may be null
1355     * @return the text escaped, "" if null String input
1356     */
1357    protected static String escapeConfluence( String text )
1358    {
1359        if ( text == null )
1360        {
1361            return "";
1362        }
1363        else
1364        {
1365            int length = text.length();
1366            StringBuilder buffer = new StringBuilder( length );
1367
1368            for ( int i = 0; i < length; ++i )
1369            {
1370                char c = text.charAt( i );
1371                switch ( c )
1372                {
1373                    case '{':
1374                        buffer.append( "\\{" );
1375                        break;
1376                    case '}':
1377                        buffer.append( "\\}" );
1378                        break;
1379                    default:
1380                         buffer.append( c );
1381                }
1382            }
1383
1384            return buffer.toString();
1385        }
1386    }
1387
1388    /**
1389     * Forward to HtmlTools.escapeHTML( text ).
1390     *
1391     * @param text the String to escape, may be null
1392     * @return the text escaped, "" if null String input
1393     * @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String)
1394     */
1395    protected static String escapeHTML( String text )
1396    {
1397        return HtmlTools.escapeHTML( text );
1398    }
1399}