001package org.apache.maven.doxia.module.apt;
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.Writer;
024import java.util.Stack;
025
026import org.apache.maven.doxia.sink.SinkEventAttributes;
027import org.apache.maven.doxia.sink.impl.AbstractTextSink;
028import org.codehaus.plexus.util.StringUtils;
029
030/**
031 * APT generator implementation.
032 * <br/>
033 * <b>Note</b>: The encoding used is UTF-8.
034 *
035 * @author eredmond
036 * @version $Id$
037 * @since 1.0
038 */
039public class AptSink
040    extends AbstractTextSink
041    implements AptMarkup
042{
043    // ----------------------------------------------------------------------
044    // Instance fields
045    // ----------------------------------------------------------------------
046
047    /**  A buffer that holds the current text when headerFlag or bufferFlag set to <code>true</code>. */
048    private StringBuffer buffer;
049
050    /**  A buffer that holds the table caption. */
051    private StringBuilder tableCaptionBuffer;
052
053    /**  author. */
054    private String author;
055
056    /**  title. */
057    private String title;
058
059    /**  date. */
060    private String date;
061
062    /** startFlag. */
063    private boolean startFlag;
064
065    /**  tableCaptionFlag. */
066    private boolean tableCaptionFlag;
067
068    /**  headerFlag. */
069    private boolean headerFlag;
070
071    /**  bufferFlag. */
072    private boolean bufferFlag;
073
074    /**  itemFlag. */
075    private boolean itemFlag;
076
077    /**  verbatimFlag. */
078    private boolean verbatimFlag;
079
080    /**  boxed verbatim. */
081    private boolean isBoxed;
082
083    /**  gridFlag for tables. */
084    private boolean gridFlag;
085
086    /**  number of cells in a table. */
087    private int cellCount;
088
089    /**  The writer to use. */
090    private final PrintWriter writer;
091
092    /**  justification of table cells. */
093    private int cellJustif[];
094
095    /**  a line of a row in a table. */
096    private String rowLine;
097
098    /**  listNestingIndent. */
099    private String listNestingIndent;
100
101    /**  listStyles. */
102    private final Stack<String> listStyles;
103
104    // ----------------------------------------------------------------------
105    // Public protected methods
106    // ----------------------------------------------------------------------
107
108    /**
109     * Constructor, initialize the Writer and the variables.
110     *
111     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
112     * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
113     */
114    protected AptSink( Writer writer )
115    {
116        this.writer = new PrintWriter( writer );
117        this.listStyles = new Stack<String>();
118
119        init();
120    }
121
122    /**
123     * Returns the buffer that holds the current text.
124     *
125     * @return A StringBuffer.
126     */
127    protected StringBuffer getBuffer()
128    {
129        return buffer;
130    }
131
132    /**
133     * Used to determine whether we are in head mode.
134     *
135     * @param headFlag True for head mode.
136     */
137    protected void setHeadFlag( boolean headFlag )
138    {
139        this.headerFlag = headFlag;
140    }
141
142    /**
143     * Reset all variables.
144     *
145     * @deprecated since 1.1.2, use {@link #init()} instead of.
146     */
147    protected void resetState()
148    {
149        init();
150    }
151
152    /** {@inheritDoc} */
153    protected void init()
154    {
155        super.init();
156
157        resetBuffer();
158
159        this.tableCaptionBuffer = new StringBuilder();
160        this.listNestingIndent = "";
161
162        this.author = null;
163        this.title = null;
164        this.date = null;
165        this.startFlag = true;
166        this.tableCaptionFlag = false;
167        this.headerFlag = false;
168        this.bufferFlag = false;
169        this.itemFlag = false;
170        this.verbatimFlag = false;
171        this.isBoxed = false;
172        this.gridFlag = false;
173        this.cellCount = 0;
174        this.cellJustif = null;
175        this.rowLine = null;
176        this.listStyles.clear();
177    }
178
179    /**
180     * Reset the StringBuilder.
181     */
182    protected void resetBuffer()
183    {
184        buffer = new StringBuffer();
185    }
186
187    /**
188     * Reset the TableCaptionBuffer.
189     */
190    protected void resetTableCaptionBuffer()
191    {
192        tableCaptionBuffer = new StringBuilder();
193    }
194
195    /** {@inheritDoc} */
196    public void head()
197    {
198        boolean startFlag = this.startFlag;
199
200        init();
201
202        headerFlag = true;
203        this.startFlag = startFlag;
204    }
205
206    /** {@inheritDoc} */
207    public void head_()
208    {
209        headerFlag = false;
210
211        if ( ! startFlag )
212        {
213            write( EOL );
214        }
215        write( HEADER_START_MARKUP + EOL );
216        if ( title != null )
217        {
218            write( " " + title + EOL );
219        }
220        write( HEADER_START_MARKUP + EOL );
221        if ( author != null )
222        {
223            write( " " + author + EOL );
224        }
225        write( HEADER_START_MARKUP + EOL );
226        if ( date != null )
227        {
228            write( " " + date + EOL );
229        }
230        write( HEADER_START_MARKUP + EOL );
231    }
232
233    /** {@inheritDoc} */
234    public void title_()
235    {
236        if ( buffer.length() > 0 )
237        {
238            title = buffer.toString();
239            resetBuffer();
240        }
241    }
242
243    /** {@inheritDoc} */
244    public void author_()
245    {
246        if ( buffer.length() > 0 )
247        {
248            author = buffer.toString();
249            resetBuffer();
250        }
251    }
252
253    /** {@inheritDoc} */
254    public void date_()
255    {
256        if ( buffer.length() > 0 )
257        {
258            date = buffer.toString();
259            resetBuffer();
260        }
261    }
262
263    /** {@inheritDoc} */
264    public void section1_()
265    {
266        write( EOL );
267    }
268
269    /** {@inheritDoc} */
270    public void section2_()
271    {
272        write( EOL );
273    }
274
275    /** {@inheritDoc} */
276    public void section3_()
277    {
278        write( EOL );
279    }
280
281    /** {@inheritDoc} */
282    public void section4_()
283    {
284        write( EOL );
285    }
286
287    /** {@inheritDoc} */
288    public void section5_()
289    {
290        write( EOL );
291    }
292
293    /** {@inheritDoc} */
294    public void sectionTitle1()
295    {
296        write( EOL );
297    }
298
299    /** {@inheritDoc} */
300    public void sectionTitle1_()
301    {
302        write( EOL + EOL );
303    }
304
305    /** {@inheritDoc} */
306    public void sectionTitle2()
307    {
308        write( EOL + SECTION_TITLE_START_MARKUP );
309    }
310
311    /** {@inheritDoc} */
312    public void sectionTitle2_()
313    {
314        write( EOL + EOL );
315    }
316
317    /** {@inheritDoc} */
318    public void sectionTitle3()
319    {
320        write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 2 ) );
321    }
322
323    /** {@inheritDoc} */
324    public void sectionTitle3_()
325    {
326        write( EOL + EOL );
327    }
328
329    /** {@inheritDoc} */
330    public void sectionTitle4()
331    {
332        write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 3 ) );
333    }
334
335    /** {@inheritDoc} */
336    public void sectionTitle4_()
337    {
338        write( EOL + EOL );
339    }
340
341    /** {@inheritDoc} */
342    public void sectionTitle5()
343    {
344        write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 4 ) );
345    }
346
347    /** {@inheritDoc} */
348    public void sectionTitle5_()
349    {
350        write( EOL + EOL );
351    }
352
353    /** {@inheritDoc} */
354    public void list()
355    {
356        listNestingIndent += " ";
357        listStyles.push( LIST_START_MARKUP );
358        write( EOL );
359    }
360
361    /** {@inheritDoc} */
362    public void list_()
363    {
364        if ( listNestingIndent.length() <= 1 )
365        {
366            write( EOL + listNestingIndent + LIST_END_MARKUP + EOL );
367        }
368        else
369        {
370            write( EOL );
371        }
372        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
373        listStyles.pop();
374        itemFlag = false;
375    }
376
377    /** {@inheritDoc} */
378    public void listItem()
379    {
380        //if ( !numberedList )
381        //write( EOL + listNestingIndent + "*" );
382        //else
383        numberedListItem();
384        itemFlag = true;
385    }
386
387    /** {@inheritDoc} */
388    public void listItem_()
389    {
390        write( EOL );
391        itemFlag = false;
392    }
393
394    /** {@inheritDoc} */
395    public void numberedList( int numbering )
396    {
397        listNestingIndent += " ";
398        write( EOL );
399
400        String style;
401        switch ( numbering )
402        {
403            case NUMBERING_UPPER_ALPHA:
404                style = String.valueOf( NUMBERING_UPPER_ALPHA_CHAR );
405                break;
406            case NUMBERING_LOWER_ALPHA:
407                style = String.valueOf( NUMBERING_LOWER_ALPHA_CHAR );
408                break;
409            case NUMBERING_UPPER_ROMAN:
410                style = String.valueOf( NUMBERING_UPPER_ROMAN_CHAR );
411                break;
412            case NUMBERING_LOWER_ROMAN:
413                style = String.valueOf( NUMBERING_LOWER_ROMAN_CHAR );
414                break;
415            case NUMBERING_DECIMAL:
416            default:
417                style = String.valueOf( NUMBERING );
418        }
419
420        listStyles.push( style );
421    }
422
423    /** {@inheritDoc} */
424    public void numberedList_()
425    {
426        if ( listNestingIndent.length() <= 1 )
427        {
428            write( EOL + listNestingIndent + LIST_END_MARKUP + EOL );
429        }
430        else
431        {
432            write( EOL );
433        }
434        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
435        listStyles.pop();
436        itemFlag = false;
437    }
438
439    /** {@inheritDoc} */
440    public void numberedListItem()
441    {
442        String style = listStyles.peek();
443        if ( style.equals( String.valueOf( STAR ) ) )
444        {
445            write( EOL + listNestingIndent + String.valueOf( STAR ) + String.valueOf( SPACE ) );
446        }
447        else
448        {
449            write( EOL + listNestingIndent + String.valueOf( LEFT_SQUARE_BRACKET )
450                + String.valueOf( LEFT_SQUARE_BRACKET ) + style + String.valueOf( RIGHT_SQUARE_BRACKET )
451                + String.valueOf( RIGHT_SQUARE_BRACKET ) + String.valueOf( SPACE ) );
452        }
453        itemFlag = true;
454    }
455
456    /** {@inheritDoc} */
457    public void numberedListItem_()
458    {
459        write( EOL );
460        itemFlag = false;
461    }
462
463    /** {@inheritDoc} */
464    public void definitionList()
465    {
466        listNestingIndent += " ";
467        listStyles.push( "" );
468        write( EOL );
469    }
470
471    /** {@inheritDoc} */
472    public void definitionList_()
473    {
474        if ( listNestingIndent.length() <= 1 )
475        {
476            write( EOL + listNestingIndent + LIST_END_MARKUP + EOL );
477        }
478        else
479        {
480            write( EOL );
481        }
482        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
483        listStyles.pop();
484        itemFlag = false;
485    }
486
487    /** {@inheritDoc} */
488    public void definedTerm()
489    {
490        write( EOL + " [" );
491    }
492
493    /** {@inheritDoc} */
494    public void definedTerm_()
495    {
496        write( "] " );
497    }
498
499    /** {@inheritDoc} */
500    public void definition()
501    {
502        itemFlag = true;
503    }
504
505    /** {@inheritDoc} */
506    public void definition_()
507    {
508        write( EOL );
509        itemFlag = false;
510    }
511
512    /** {@inheritDoc} */
513    public void pageBreak()
514    {
515        write( EOL + PAGE_BREAK + EOL );
516    }
517
518    /** {@inheritDoc} */
519    public void paragraph()
520    {
521        if ( itemFlag )
522        {
523            write( EOL + EOL + "  " + listNestingIndent );
524        }
525        else
526        {
527            write( EOL + " " );
528        }
529    }
530
531    /** {@inheritDoc} */
532    public void paragraph_()
533    {
534        write( EOL + EOL );
535    }
536
537    /** {@inheritDoc} */
538    public void verbatim( boolean boxed )
539    {
540        verbatimFlag = true;
541        this.isBoxed = boxed;
542        write( EOL );
543        if ( boxed )
544        {
545            write( EOL + BOXED_VERBATIM_START_MARKUP + EOL );
546        }
547        else
548        {
549            write( EOL + NON_BOXED_VERBATIM_START_MARKUP + EOL );
550        }
551    }
552
553    /** {@inheritDoc} */
554    public void verbatim_()
555    {
556        if ( isBoxed )
557        {
558            write( EOL + BOXED_VERBATIM_END_MARKUP + EOL );
559        }
560        else
561        {
562            write( EOL + NON_BOXED_VERBATIM_END_MARKUP + EOL );
563        }
564        isBoxed = false;
565        verbatimFlag = false;
566    }
567
568    /** {@inheritDoc} */
569    public void horizontalRule()
570    {
571        write( EOL + HORIZONTAL_RULE_MARKUP + EOL );
572    }
573
574    /** {@inheritDoc} */
575    public void table()
576    {
577        write( EOL );
578    }
579
580    /** {@inheritDoc} */
581    public void table_()
582    {
583        if ( rowLine != null )
584        {
585            write( rowLine );
586        }
587        rowLine = null;
588
589        if ( tableCaptionBuffer.length() > 0 )
590        {
591            text( tableCaptionBuffer.toString() + EOL );
592        }
593
594        resetTableCaptionBuffer();
595    }
596
597    /** {@inheritDoc} */
598    public void tableRows( int justification[], boolean grid )
599    {
600        cellJustif = justification;
601        gridFlag = grid;
602    }
603
604    /** {@inheritDoc} */
605    public void tableRows_()
606    {
607        cellJustif = null;
608        gridFlag = false;
609    }
610
611    /** {@inheritDoc} */
612    public void tableRow()
613    {
614        bufferFlag = true;
615        cellCount = 0;
616    }
617
618    /** {@inheritDoc} */
619    public void tableRow_()
620    {
621        bufferFlag = false;
622
623        // write out the header row first, then the data in the buffer
624        buildRowLine();
625
626        write( rowLine );
627
628        // TODO: This will need to be more clever, for multi-line cells
629        if ( gridFlag )
630        {
631            write( TABLE_ROW_SEPARATOR_MARKUP );
632        }
633
634        write( buffer.toString() );
635
636        resetBuffer();
637
638        write( EOL );
639
640        // only reset cell count if this is the last row
641        cellCount = 0;
642    }
643
644    /** Construct a table row. */
645    private void buildRowLine()
646    {
647        StringBuilder rLine = new StringBuilder();
648        rLine.append( TABLE_ROW_START_MARKUP );
649
650        for ( int i = 0; i < cellCount; i++ )
651        {
652            if ( cellJustif != null )
653            {
654                switch ( cellJustif[i] )
655                {
656                case 1:
657                    rLine.append( TABLE_COL_LEFT_ALIGNED_MARKUP );
658                    break;
659                case 2:
660                    rLine.append( TABLE_COL_RIGHT_ALIGNED_MARKUP );
661                    break;
662                default:
663                    rLine.append( TABLE_COL_CENTERED_ALIGNED_MARKUP );
664                }
665            }
666            else
667            {
668                rLine.append( TABLE_COL_CENTERED_ALIGNED_MARKUP );
669            }
670        }
671        rLine.append( EOL );
672
673        this.rowLine = rLine.toString();
674    }
675
676    /** {@inheritDoc} */
677    public void tableCell()
678    {
679        tableCell( false );
680    }
681
682    /** {@inheritDoc} */
683    public void tableHeaderCell()
684    {
685        tableCell( true );
686    }
687
688    /**
689     * Starts a table cell.
690     *
691     * @param headerRow If this cell is part of a header row.
692     */
693    public void tableCell( boolean headerRow )
694    {
695        if ( headerRow )
696        {
697            buffer.append( TABLE_CELL_SEPARATOR_MARKUP );
698        }
699    }
700
701    /** {@inheritDoc} */
702    public void tableCell_()
703    {
704        endTableCell();
705    }
706
707    /** {@inheritDoc} */
708    public void tableHeaderCell_()
709    {
710        endTableCell();
711    }
712
713    /**
714     * Ends a table cell.
715     */
716    private void endTableCell()
717    {
718        buffer.append( TABLE_CELL_SEPARATOR_MARKUP );
719        cellCount++;
720    }
721
722    /** {@inheritDoc} */
723    public void tableCaption()
724    {
725        tableCaptionFlag = true;
726    }
727
728    /** {@inheritDoc} */
729    public void tableCaption_()
730    {
731        tableCaptionFlag = false;
732    }
733
734    /** {@inheritDoc} */
735    public void figureCaption_()
736    {
737        write( EOL );
738    }
739
740    /** {@inheritDoc} */
741    public void figureGraphics( String name )
742    {
743        write( EOL + "[" + name + "] " );
744    }
745
746    /** {@inheritDoc} */
747    public void anchor( String name )
748    {
749        write( ANCHOR_START_MARKUP );
750    }
751
752    /** {@inheritDoc} */
753    public void anchor_()
754    {
755        write( ANCHOR_END_MARKUP );
756    }
757
758    /** {@inheritDoc} */
759    public void link( String name )
760    {
761        if ( !headerFlag )
762        {
763            write( LINK_START_1_MARKUP );
764            text( name.startsWith( "#" ) ? name.substring( 1 ) : name );
765            write( LINK_START_2_MARKUP );
766        }
767    }
768
769    /** {@inheritDoc} */
770    public void link_()
771    {
772        if ( !headerFlag )
773        {
774            write( LINK_END_MARKUP );
775        }
776    }
777
778    /**
779     * A link with a target.
780     *
781     * @param name The name of the link.
782     * @param target The link target.
783     */
784    public void link( String name, String target )
785    {
786        if ( !headerFlag )
787        {
788            write( LINK_START_1_MARKUP );
789            text( target );
790            write( LINK_START_2_MARKUP );
791            text( name );
792        }
793    }
794
795    /** {@inheritDoc} */
796    public void italic()
797    {
798        if ( !headerFlag )
799        {
800            write( ITALIC_START_MARKUP );
801        }
802    }
803
804    /** {@inheritDoc} */
805    public void italic_()
806    {
807        if ( !headerFlag )
808        {
809            write( ITALIC_END_MARKUP );
810        }
811    }
812
813    /** {@inheritDoc} */
814    public void bold()
815    {
816        if ( !headerFlag )
817        {
818            write( BOLD_START_MARKUP );
819        }
820    }
821
822    /** {@inheritDoc} */
823    public void bold_()
824    {
825        if ( !headerFlag )
826        {
827            write( BOLD_END_MARKUP );
828        }
829    }
830
831    /** {@inheritDoc} */
832    public void monospaced()
833    {
834        if ( !headerFlag )
835        {
836            write( MONOSPACED_START_MARKUP );
837        }
838    }
839
840    /** {@inheritDoc} */
841    public void monospaced_()
842    {
843        if ( !headerFlag )
844        {
845            write( MONOSPACED_END_MARKUP );
846        }
847    }
848
849    /** {@inheritDoc} */
850    public void lineBreak()
851    {
852        if ( headerFlag || bufferFlag )
853        {
854            buffer.append( EOL );
855        }
856        else if ( verbatimFlag )
857        {
858            write( EOL );
859        }
860        else
861        {
862            write( "\\" + EOL );
863        }
864    }
865
866    /** {@inheritDoc} */
867    public void nonBreakingSpace()
868    {
869        if ( headerFlag || bufferFlag )
870        {
871            buffer.append( NON_BREAKING_SPACE_MARKUP );
872        }
873        else
874        {
875            write( NON_BREAKING_SPACE_MARKUP );
876        }
877    }
878
879    /** {@inheritDoc} */
880    public void text( String text )
881    {
882        if ( tableCaptionFlag )
883        {
884            tableCaptionBuffer.append( text );
885        }
886        else if ( headerFlag || bufferFlag )
887        {
888            buffer.append( text );
889        }
890        else if ( verbatimFlag )
891        {
892            verbatimContent( text );
893        }
894        else
895        {
896            content( text );
897        }
898    }
899
900    /** {@inheritDoc} */
901    public void rawText( String text )
902    {
903        write( text );
904    }
905
906    /** {@inheritDoc} */
907    public void comment( String comment )
908    {
909        rawText( ( startFlag ? "" : EOL ) + COMMENT + COMMENT + comment );
910    }
911
912    /**
913     * {@inheritDoc}
914     *
915     * Unkown events just log a warning message but are ignored otherwise.
916     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
917     */
918    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
919    {
920        getLog().warn( "[Apt Sink] Unknown Sink event: '" + name + "', ignoring!" );
921    }
922
923    /**
924     * Write text to output.
925     *
926     * @param text The text to write.
927     */
928    protected void write( String text )
929    {
930        startFlag = false;
931        writer.write( unifyEOLs( text ) );
932    }
933
934    /**
935     * Write Apt escaped text to output.
936     *
937     * @param text The text to write.
938     */
939    protected void content( String text )
940    {
941        write( escapeAPT( text ) );
942    }
943
944    /**
945     * Write Apt escaped text to output.
946     *
947     * @param text The text to write.
948     */
949    protected void verbatimContent( String text )
950    {
951        write( escapeAPT( text ) );
952    }
953
954    /** {@inheritDoc} */
955    public void flush()
956    {
957        writer.flush();
958    }
959
960    /** {@inheritDoc} */
961    public void close()
962    {
963        writer.close();
964
965        init();
966    }
967
968    // ----------------------------------------------------------------------
969    // Private methods
970    // ----------------------------------------------------------------------
971
972    /**
973     * Escape special characters in a text in APT:
974     *
975     * <pre>
976     * \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\
977     * </pre>
978     *
979     * @param text the String to escape, may be null
980     * @return the text escaped, "" if null String input
981     */
982    private static String escapeAPT( String text )
983    {
984        if ( text == null )
985        {
986            return "";
987        }
988
989        int length = text.length();
990        StringBuilder buffer = new StringBuilder( length );
991
992        for ( int i = 0; i < length; ++i )
993        {
994            char c = text.charAt( i );
995            switch ( c )
996            { // 0080
997                case '\\':
998                case '~':
999                case '=':
1000                case '-':
1001                case '+':
1002                case '*':
1003                case '[':
1004                case ']':
1005                case '<':
1006                case '>':
1007                case '{':
1008                case '}':
1009                    buffer.append( '\\' );
1010                    buffer.append( c );
1011                    break;
1012                default:
1013                    buffer.append( c );
1014            }
1015        }
1016
1017        return buffer.toString();
1018    }
1019}