001package org.apache.maven.doxia.module.latex;
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 org.apache.maven.doxia.sink.Sink;
023import org.apache.maven.doxia.sink.SinkEventAttributes;
024import org.apache.maven.doxia.sink.impl.AbstractTextSink;
025import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
026import org.apache.maven.doxia.util.DoxiaUtils;
027import org.apache.maven.doxia.util.LineBreaker;
028
029import org.codehaus.plexus.util.IOUtil;
030import org.codehaus.plexus.util.StringUtils;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.Writer;
035import java.util.ArrayList;
036import java.util.List;
037import java.util.Locale;
038import java.util.Stack;
039
040/**
041 * Latex Sink implementation.
042 * <br>
043 * <b>Note</b>: The encoding used is UTF-8.
044 *
045 * @since 1.0
046 */
047public class LatexSink
048    extends AbstractTextSink
049{
050    /**
051     * Flag that indicates if the document to be written is only a fragment.
052     *
053     * This implies that <code>\\begin{document}</code>, <code>\\title{..}</code> will not be output.
054     */
055    private final boolean fragmentDocument;
056
057    private boolean ignoreText;
058
059    private final LineBreaker out;
060
061    private final String sinkCommands;
062
063    private final String preamble;
064
065    private boolean titleFlag;
066
067    private int numberedListNesting;
068
069    private boolean verbatimFlag;
070
071    private boolean figureFlag;
072
073    private boolean tableFlag;
074
075    private boolean gridFlag;
076
077    private int[] cellJustif;
078
079    private int cellCount;
080
081    private boolean isTitle;
082
083    private String title;
084
085    /** Keep track of the closing tags for inline events. */
086    protected Stack<List<String>> inlineStack = new Stack<>();
087
088    // ----------------------------------------------------------------------
089    //
090    // ----------------------------------------------------------------------
091
092    /**
093     * Constructor, initialize the Writer and the variables.
094     *
095     * @param out not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
096     * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
097     */
098    protected LatexSink( Writer out )
099    {
100        this( out, null, null );
101    }
102
103    /**
104     * Constructor, initialize the Writer and the variables.
105     *
106     * @param out not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
107     * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
108     * @param sinkCommands A String representation of commands that go before \documentclass.
109     * @param preamble A String representation of commands that go between \documentclass and \begin{document}.
110     */
111    protected LatexSink( Writer out, String sinkCommands, String preamble )
112    {
113        this( out, sinkCommands, preamble, false );
114    }
115
116    /**
117     * Constructor, initialize the Writer and the variables.
118     *
119     * @param out not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
120     * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
121     * @param sinkCommands A String representation of commands that go before \documentclass.
122     * @param preamble A String representation of commands that go between \documentclass and \begin{document}.
123     * @param fragmentDocument If this receives events that that are only part of a document.
124     * Typically, headers are omitted if this is true.
125     */
126    protected LatexSink( Writer out, String sinkCommands, String preamble, boolean fragmentDocument )
127    {
128        this.out = new LineBreaker( out );
129
130        if ( sinkCommands == null )
131        {
132            sinkCommands = defaultSinkCommands();
133        }
134        if ( preamble == null )
135        {
136            preamble = defaultPreamble();
137        }
138
139        this.sinkCommands = sinkCommands;
140        this.preamble = preamble;
141        this.fragmentDocument = fragmentDocument;
142
143        init();
144    }
145
146    // ----------------------------------------------------------------------
147    // Overridables
148    // ----------------------------------------------------------------------
149
150    /**
151     * Returns a default \documentclass declaration.
152     *
153     * @return String.
154     */
155    protected String getDocumentStart()
156    {
157        return "\\documentclass[a4paper]{article}" + EOL + EOL;
158    }
159
160    /**
161     * Returns a default \begin{document} declaration.
162     *
163     * @return String.
164     */
165    protected String getDocumentBegin()
166    {
167        return "\\begin{document}" + EOL + EOL;
168    }
169
170    /**
171     * Returns a default \end{document} declaration.
172     *
173     * @return String.
174     */
175    protected String getDocumentEnd()
176    {
177        return "\\end{document}" + EOL;
178    }
179
180    // ----------------------------------------------------------------------
181    // Sink Implementation
182    // ----------------------------------------------------------------------
183
184    /**
185     * {@inheritDoc}
186     */
187    public void head()
188    {
189        head( null );
190    }
191
192    /** {@inheritDoc} */
193    public void head( SinkEventAttributes attributes )
194    {
195        init();
196
197        if ( !fragmentDocument )
198        {
199            markup( sinkCommands );
200
201            markup( getDocumentStart() );
202
203            markup( preamble );
204
205            markup( getDocumentBegin() );
206        }
207    }
208
209    /**
210     * {@inheritDoc}
211     */
212    public void body()
213    {
214        body( null );
215    }
216
217    /** {@inheritDoc} */
218    public void body( SinkEventAttributes attributes )
219    {
220        if ( titleFlag )
221        {
222            if ( fragmentDocument  )
223            {
224                markup( "\\section" );
225            }
226            else
227            {
228                titleFlag = false;
229                markup( "\\maketitle" + EOL + EOL );
230            }
231        }
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    public void body_()
238    {
239        if ( !fragmentDocument )
240        {
241            markup( getDocumentEnd() );
242        }
243
244        flush();
245    }
246
247    /**
248     * {@inheritDoc}
249     */
250    public void title()
251    {
252        title( null );
253    }
254
255    /** {@inheritDoc} */
256    public void title( SinkEventAttributes attributes )
257    {
258        if ( !fragmentDocument )
259        {
260            titleFlag = true;
261            markup( "\\title{" );
262        }
263        else
264        {
265            ignoreText = true;
266        }
267    }
268
269    /**
270     * {@inheritDoc}
271     */
272    public void title_()
273    {
274        if ( !fragmentDocument )
275        {
276            markup( "}" + EOL );
277        }
278        else
279        {
280            ignoreText = false;
281        }
282    }
283
284    /**
285     * {@inheritDoc}
286     */
287    public void author()
288    {
289        author( null );
290    }
291
292    /** {@inheritDoc} */
293    public void author( SinkEventAttributes attributes )
294    {
295        if ( !fragmentDocument )
296        {
297            markup( "\\author{" );
298        }
299        else
300        {
301            ignoreText = true;
302        }
303    }
304
305    /**
306     * {@inheritDoc}
307     */
308    public void author_()
309    {
310        if ( !fragmentDocument )
311        {
312            markup( "}" + EOL );
313        }
314        else
315        {
316            ignoreText = false;
317        }
318    }
319
320    /**
321     * {@inheritDoc}
322     */
323    public void date()
324    {
325        date( null );
326    }
327
328    /** {@inheritDoc} */
329    public void date( SinkEventAttributes attributes )
330    {
331        if ( !fragmentDocument )
332        {
333            markup( "\\date{" );
334        }
335        else
336        {
337            ignoreText = true;
338        }
339    }
340
341    /**
342     * {@inheritDoc}
343     */
344    public void date_()
345    {
346        if ( !fragmentDocument )
347        {
348            markup( "}" + EOL );
349        }
350        else
351        {
352            ignoreText = false;
353        }
354    }
355
356    /** {@inheritDoc} */
357    public void sectionTitle( int level, SinkEventAttributes attributes )
358    {
359        isTitle = true;
360    }
361
362    /** {@inheritDoc} */
363    public void sectionTitle_( int level )
364    {
365        String command;
366        switch ( level )
367        {
368            case SECTION_LEVEL_1:
369                command = "section";
370                break;
371            case SECTION_LEVEL_2:
372                command = "subsection";
373                break;
374            case SECTION_LEVEL_3:
375                command = "subsubsection";
376                break;
377            case SECTION_LEVEL_4:
378                command = "paragraph";
379                break;
380            case SECTION_LEVEL_5:
381                command = "subparagraph";
382                break;
383            default:
384                throw new IllegalArgumentException( "Not a section level: " + level );
385        }
386
387        isTitle = false;
388
389        if ( StringUtils.isNotEmpty( title ) )
390        {
391            markup( EOL + "\\" + command + "{" + title + "}" + EOL );
392
393            title = null;
394        }
395    }
396
397    // ----------------------------------------------------------------------
398    // Section Title 1
399    // ----------------------------------------------------------------------
400
401    /**
402     * {@inheritDoc}
403     */
404    public void sectionTitle1()
405    {
406        sectionTitle( SECTION_LEVEL_1, null );
407    }
408
409    /**
410     * {@inheritDoc}
411     */
412    public void sectionTitle1_()
413    {
414        sectionTitle_( SECTION_LEVEL_1 );
415    }
416
417    // ----------------------------------------------------------------------
418    // Section Title 2
419    // ----------------------------------------------------------------------
420
421    /**
422     * {@inheritDoc}
423     */
424    public void sectionTitle2()
425    {
426        sectionTitle( SECTION_LEVEL_2, null );
427    }
428
429    /**
430     * {@inheritDoc}
431     */
432    public void sectionTitle2_()
433    {
434        sectionTitle_( SECTION_LEVEL_2 );
435    }
436
437    // ----------------------------------------------------------------------
438    // Section Title 3
439    // ----------------------------------------------------------------------
440
441    /**
442     * {@inheritDoc}
443     */
444    public void sectionTitle3()
445    {
446        sectionTitle( SECTION_LEVEL_3, null );
447    }
448
449    /**
450     * {@inheritDoc}
451     */
452    public void sectionTitle3_()
453    {
454        sectionTitle_( SECTION_LEVEL_3 );
455    }
456
457    // ----------------------------------------------------------------------
458    // Section Title 4
459    // ----------------------------------------------------------------------
460
461    /**
462     * {@inheritDoc}
463     */
464    public void sectionTitle4()
465    {
466        sectionTitle( SECTION_LEVEL_4, null );
467    }
468
469    /**
470     * {@inheritDoc}
471     */
472    public void sectionTitle4_()
473    {
474        sectionTitle_( SECTION_LEVEL_4 );
475    }
476
477    // ----------------------------------------------------------------------
478    // Section Title 5
479    // ----------------------------------------------------------------------
480
481    /**
482     * {@inheritDoc}
483     */
484    public void sectionTitle5()
485    {
486        sectionTitle( SECTION_LEVEL_5, null );
487    }
488
489    /**
490     * {@inheritDoc}
491     */
492    public void sectionTitle5_()
493    {
494        sectionTitle_( SECTION_LEVEL_5 );
495    }
496
497    // ----------------------------------------------------------------------
498    // List
499    // ----------------------------------------------------------------------
500
501    /**
502     * {@inheritDoc}
503     */
504    public void list()
505    {
506        list( null );
507    }
508
509    /** {@inheritDoc} */
510    public void list( SinkEventAttributes attributes )
511    {
512        markup( EOL + "\\begin{itemize}" );
513    }
514
515    /**
516     * {@inheritDoc}
517     */
518    public void list_()
519    {
520        markup( EOL + "\\end{itemize}" + EOL );
521    }
522
523    /**
524     * {@inheritDoc}
525     */
526    public void listItem()
527    {
528        listItem( null );
529    }
530
531    /** {@inheritDoc} */
532    public void listItem( SinkEventAttributes attributes )
533    {
534        markup( EOL + "\\item " );
535    }
536
537    /** {@inheritDoc} */
538    public void numberedList( int numbering )
539    {
540        numberedList( numbering, null );
541    }
542
543    /** {@inheritDoc} */
544    public void numberedList( int numbering, SinkEventAttributes attributes )
545    {
546        ++numberedListNesting;
547
548        String counter;
549        switch ( numberedListNesting )
550        {
551            case 1:
552                counter = "enumi";
553                break;
554            case 2:
555                counter = "enumii";
556                break;
557            case 3:
558                counter = "enumiii";
559                break;
560            case 4:
561            default:
562                counter = "enumiv";
563        }
564
565        String style;
566        switch ( numbering )
567        {
568            case NUMBERING_UPPER_ALPHA:
569                style = "Alph";
570                break;
571            case NUMBERING_LOWER_ALPHA:
572                style = "alph";
573                break;
574            case NUMBERING_UPPER_ROMAN:
575                style = "Roman";
576                break;
577            case NUMBERING_LOWER_ROMAN:
578                style = "roman";
579                break;
580            case NUMBERING_DECIMAL:
581            default:
582                style = "arabic";
583        }
584
585        markup( EOL + "\\begin{enumerate}" + EOL );
586        markup( "\\renewcommand{\\the" + counter + "}{\\" + style + "{" + counter + "}}" + EOL );
587    }
588
589    /**
590     * {@inheritDoc}
591     */
592    public void numberedList_()
593    {
594        markup( EOL + "\\end{enumerate}" + EOL );
595        --numberedListNesting;
596    }
597
598    /**
599     * {@inheritDoc}
600     */
601    public void numberedListItem()
602    {
603        numberedListItem( null );
604    }
605
606    /** {@inheritDoc} */
607    public void numberedListItem( SinkEventAttributes attributes )
608    {
609        markup( "\\item " );
610    }
611
612    /**
613     * {@inheritDoc}
614     */
615    public void definitionList()
616    {
617        definitionList( null );
618    }
619
620    /** {@inheritDoc} */
621    public void definitionList( SinkEventAttributes attributes )
622    {
623        markup( EOL + "\\begin{description}" );
624    }
625
626    /**
627     * {@inheritDoc}
628     */
629    public void definitionList_()
630    {
631        markup( EOL + "\\end{description}" + EOL );
632    }
633
634    /**
635     * {@inheritDoc}
636     */
637    public void definedTerm()
638    {
639        definedTerm( null );
640    }
641
642    /** {@inheritDoc} */
643    public void definedTerm( SinkEventAttributes attributes )
644    {
645        markup( EOL + "\\item[\\mbox{" );
646    }
647
648    /**
649     * {@inheritDoc}
650     */
651    public void definedTerm_()
652    {
653        markup( "}] " );
654    }
655
656    /**
657     * {@inheritDoc}
658     */
659    public void definitionListItem()
660    {
661        definitionListItem( null );
662    }
663
664    /** {@inheritDoc} */
665    public void definitionListItem( SinkEventAttributes attributes )
666    {
667        // nop
668    }
669
670    /**
671     * {@inheritDoc}
672     */
673    public void definitionListItem_()
674    {
675        // nop
676    }
677
678    /**
679     * {@inheritDoc}
680     */
681    public void definition()
682    {
683        definition( null );
684    }
685
686    /** {@inheritDoc} */
687    public void definition( SinkEventAttributes attributes )
688    {
689        // nop
690    }
691
692    /**
693     * {@inheritDoc}
694     */
695    public void definition_()
696    {
697        // nop
698    }
699
700    // ----------------------------------------------------------------------
701    // Figure
702    // ----------------------------------------------------------------------
703
704    /**
705     * {@inheritDoc}
706     */
707    public void figure()
708    {
709        figure( null );
710    }
711
712    /** {@inheritDoc} */
713    public void figure( SinkEventAttributes attributes )
714    {
715        figureFlag = true;
716        markup( EOL + "\\begin{figure}[htb]" + EOL );
717    }
718
719    /**
720     * {@inheritDoc}
721     */
722    public void figure_()
723    {
724        markup( "\\end{figure}" + EOL );
725        figureFlag = false;
726    }
727
728    /** {@inheritDoc} */
729    public void figureGraphics( String name )
730    {
731        figureGraphics( name, null );
732    }
733
734    /** {@inheritDoc} */
735    public void figureGraphics( String src, SinkEventAttributes attributes )
736    {
737        if ( !src.toLowerCase( Locale.ENGLISH ).endsWith( ".eps" ) )
738        {
739            getLog().warn( "[Latex Sink] Found non-eps figure graphics!" );
740        }
741
742        markup( "\\begin{center}" + EOL );
743        markup( "\\includegraphics{" + src + "}" + EOL );
744        markup( "\\end{center}" + EOL );
745    }
746
747    /**
748     * {@inheritDoc}
749     */
750    public void figureCaption()
751    {
752        figureCaption( null );
753    }
754
755    /** {@inheritDoc} */
756    public void figureCaption( SinkEventAttributes attributes )
757    {
758        markup( "\\caption{" );
759    }
760
761    /**
762     * {@inheritDoc}
763     */
764    public void figureCaption_()
765    {
766        markup( "}" + EOL );
767    }
768
769    // ----------------------------------------------------------------------
770    // Table
771    // ----------------------------------------------------------------------
772
773    /**
774     * {@inheritDoc}
775     */
776    public void table()
777    {
778        table( null );
779    }
780
781    /** {@inheritDoc} */
782    public void table( SinkEventAttributes attributes )
783    {
784        tableFlag = true;
785        markup( EOL + "\\begin{table}[htp]" + EOL );
786    }
787
788    /**
789     * {@inheritDoc}
790     */
791    public void table_()
792    {
793        markup( "\\end{table}" + EOL );
794        tableFlag = false;
795    }
796
797    /** {@inheritDoc} */
798    public void tableRows( int[] justification, boolean grid )
799
800    {
801        StringBuilder justif = new StringBuilder();
802        for ( int i1 : justification )
803        {
804            if ( grid )
805            {
806                justif.append( '|' );
807            }
808            switch ( i1 )
809            {
810                case Sink.JUSTIFY_CENTER:
811                    justif.append( 'c' );
812                    break;
813                case Sink.JUSTIFY_LEFT:
814                    justif.append( 'l' );
815                    break;
816                case Sink.JUSTIFY_RIGHT:
817                    justif.append( 'r' );
818                    break;
819                default:
820                    break;
821            }
822        }
823        if ( grid )
824        {
825            justif.append( '|' );
826        }
827
828        markup( "\\begin{center}" + EOL );
829        markup( "\\begin{tabular}{" + justif.toString() + "}" + EOL );
830        if ( grid )
831        {
832            markup( "\\hline" + EOL );
833        }
834        gridFlag = grid;
835        cellJustif = justification;
836    }
837
838    /**
839     * {@inheritDoc}
840     */
841    public void tableRows_()
842    {
843        markup( "\\end{tabular}" + EOL );
844        markup( "\\end{center}" + EOL );
845
846        gridFlag = false;
847        cellJustif = null;
848    }
849
850    /**
851     * {@inheritDoc}
852     */
853    public void tableRow()
854    {
855        tableRow( null );
856    }
857
858    /** {@inheritDoc} */
859    public void tableRow( SinkEventAttributes attributes )
860    {
861        cellCount = 0;
862    }
863
864    /**
865     * {@inheritDoc}
866     */
867    public void tableRow_()
868    {
869        markup( "\\\\" + EOL );
870        if ( gridFlag || lastCellWasHeader )
871        {
872            markup( "\\hline" + EOL );
873        }
874        cellCount = 0;
875        lastCellWasHeader = false;
876    }
877
878    /**
879     * {@inheritDoc}
880     */
881    public void tableCell()
882    {
883        tableCell( (SinkEventAttributes) null );
884    }
885
886    /** {@inheritDoc} */
887    public void tableCell( String width )
888    {
889        SinkEventAttributeSet att = new SinkEventAttributeSet();
890        att.addAttribute( javax.swing.text.html.HTML.Attribute.WIDTH, width );
891
892        tableCell( att );
893    }
894
895    /** {@inheritDoc} */
896    public void tableCell( SinkEventAttributes attributes )
897    {
898        tableCell( false );
899    }
900
901    /**
902     * {@inheritDoc}
903     */
904    public void tableCell_()
905    {
906        markup( "\\end{tabular}" );
907        ++cellCount;
908    }
909
910    /**
911     * {@inheritDoc}
912     */
913    public void tableHeaderCell()
914    {
915        tableCell( (SinkEventAttributes) null );
916    }
917
918    /** {@inheritDoc} */
919    public void tableHeaderCell( String width )
920    {
921        SinkEventAttributeSet att = new SinkEventAttributeSet();
922        att.addAttribute( javax.swing.text.html.HTML.Attribute.WIDTH, width );
923
924        tableHeaderCell( att );
925    }
926
927    /** {@inheritDoc} */
928    public void tableHeaderCell( SinkEventAttributes attributes )
929    {
930        tableCell( true );
931    }
932
933    /**
934     * {@inheritDoc}
935     */
936    public void tableHeaderCell_()
937    {
938        tableCell_();
939    }
940
941    private boolean lastCellWasHeader = false;
942
943    /**
944     * Starts a table cell.
945     *
946     * @param header True if this is a header cell.
947     */
948    private void tableCell( boolean header )
949    {
950        lastCellWasHeader = header;
951
952        if ( cellCount > 0 )
953        {
954            markup( " &" + EOL );
955        }
956
957        char justif;
958        switch ( cellJustif[cellCount] )
959        {
960            case Sink.JUSTIFY_LEFT:
961                justif = 'l';
962                break;
963            case Sink.JUSTIFY_RIGHT:
964                justif = 'r';
965                break;
966            case Sink.JUSTIFY_CENTER:
967            default:
968                justif = 'c';
969                break;
970        }
971        markup( "\\begin{tabular}[t]{" + justif + "}" );
972    }
973
974    /**
975     * {@inheritDoc}
976     */
977    public void tableCaption()
978    {
979        tableCaption( null );
980    }
981
982    /** {@inheritDoc} */
983    public void tableCaption( SinkEventAttributes attributes )
984    {
985        markup( "\\caption{" );
986    }
987
988    /**
989     * {@inheritDoc}
990     */
991    public void tableCaption_()
992    {
993        markup( "}" + EOL );
994    }
995
996    /**
997     * {@inheritDoc}
998     */
999    public void paragraph()
1000    {
1001        paragraph( null );
1002    }
1003
1004    /** {@inheritDoc} */
1005    public void paragraph( SinkEventAttributes attributes )
1006    {
1007        markup( EOL + EOL );
1008    }
1009
1010    /**
1011     * {@inheritDoc}
1012     */
1013    public void paragraph_()
1014    {
1015        markup( EOL );
1016    }
1017
1018    /** {@inheritDoc} */
1019    public void verbatim( boolean boxed )
1020    {
1021        verbatim( boxed ? SinkEventAttributeSet.BOXED : null );
1022    }
1023
1024    /**
1025     * {@inheritDoc}
1026     *
1027     * @param attributes a {@link org.apache.maven.doxia.sink.SinkEventAttributes} object.
1028     */
1029    public void verbatim( SinkEventAttributes attributes )
1030    {
1031        boolean boxed = false;
1032
1033        if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) )
1034        {
1035            boxed = "boxed".equals(
1036                attributes.getAttribute( SinkEventAttributes.DECORATION ) );
1037        }
1038
1039        markup( EOL + "\\begin{small}" + EOL );
1040
1041        if ( boxed )
1042        {
1043            markup( "\\begin{Verbatim}[frame=single]" + EOL );
1044        }
1045        else
1046        {
1047            markup( "\\begin{Verbatim}" + EOL );
1048        }
1049
1050        verbatimFlag = true;
1051    }
1052
1053    /**
1054     * {@inheritDoc}
1055     */
1056    public void verbatim_()
1057    {
1058        markup( EOL + "\\end{Verbatim}" + EOL );
1059        markup( "\\end{small}" + EOL );
1060
1061        verbatimFlag = false;
1062    }
1063
1064    /**
1065     * {@inheritDoc}
1066     */
1067    public void horizontalRule()
1068    {
1069        horizontalRule( null );
1070    }
1071
1072    /** {@inheritDoc} */
1073    public void horizontalRule( SinkEventAttributes attributes )
1074    {
1075        markup( EOL + "\\begin{center}\\rule[0.5ex]{\\linewidth}{1pt}\\end{center}" + EOL );
1076    }
1077
1078    /**
1079     * {@inheritDoc}
1080     */
1081    public void pageBreak()
1082    {
1083        markup( EOL + "\\newpage" + EOL );
1084    }
1085
1086    /** {@inheritDoc} */
1087    public void anchor( String name )
1088    {
1089        anchor( name, null );
1090    }
1091
1092    /** {@inheritDoc} */
1093    public void anchor( String name, SinkEventAttributes attributes )
1094    {
1095        markup( "\\hypertarget{" + name + "}{" );
1096    }
1097
1098    /**
1099     * {@inheritDoc}
1100     */
1101    public void anchor_()
1102    {
1103        markup( "}" );
1104    }
1105
1106    /** {@inheritDoc} */
1107    public void link( String name )
1108    {
1109        link( name, null );
1110    }
1111
1112    /** {@inheritDoc} */
1113    public void link( String name, SinkEventAttributes attributes )
1114    {
1115        // TODO: use \\url for simple links
1116        if ( DoxiaUtils.isExternalLink( name ) )
1117        {
1118            markup( "\\href{" + name + "}{" );
1119        }
1120        else
1121        {
1122            markup( "\\hyperlink{" + name + "}{" );
1123        }
1124    }
1125
1126    /**
1127     * {@inheritDoc}
1128     */
1129    public void link_()
1130    {
1131        markup( "}" );
1132    }
1133
1134    /**
1135     * {@inheritDoc}
1136     */
1137    public void inline()
1138    {
1139        inline( null );
1140    }
1141
1142    /** {@inheritDoc} */
1143    public void inline( SinkEventAttributes attributes )
1144    {
1145        List<String> tags = new ArrayList<>();
1146
1147        if ( attributes != null )
1148        {
1149
1150            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) )
1151            {
1152                markup( "\\textit{" );
1153                tags.add( 0, "}" );
1154            }
1155
1156            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) )
1157            {
1158                markup( "\\textbf{" );
1159                tags.add( 0, "}" );
1160            }
1161
1162            if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) )
1163            {
1164                markup( "\\texttt{\\small " );
1165                tags.add( 0, "}" );
1166            }
1167
1168        }
1169
1170        inlineStack.push( tags );
1171    }
1172
1173    /**
1174     * {@inheritDoc}
1175     */
1176    public void inline_()
1177    {
1178        for ( String tag: inlineStack.pop() )
1179        {
1180            markup( tag );
1181        }
1182    }
1183
1184    /**
1185     * {@inheritDoc}
1186     */
1187    public void italic()
1188    {
1189        inline( SinkEventAttributeSet.Semantics.ITALIC );
1190    }
1191
1192    /**
1193     * {@inheritDoc}
1194     */
1195    public void italic_()
1196    {
1197        inline_();
1198    }
1199
1200    /**
1201     * {@inheritDoc}
1202     */
1203    public void bold()
1204    {
1205        inline( SinkEventAttributeSet.Semantics.BOLD );
1206    }
1207
1208    /**
1209     * {@inheritDoc}
1210     */
1211    public void bold_()
1212    {
1213        inline_();
1214    }
1215
1216    /**
1217     * {@inheritDoc}
1218     */
1219    public void monospaced()
1220    {
1221        inline( SinkEventAttributeSet.Semantics.CODE );
1222    }
1223
1224    /**
1225     * {@inheritDoc}
1226     */
1227    public void monospaced_()
1228    {
1229        inline_();
1230    }
1231
1232    /**
1233     * {@inheritDoc}
1234     */
1235    public void lineBreak()
1236    {
1237        lineBreak( null );
1238    }
1239
1240    /** {@inheritDoc} */
1241    public void lineBreak( SinkEventAttributes attributes )
1242    {
1243        markup( ( figureFlag || tableFlag || titleFlag || verbatimFlag ) ? EOL : "\\newline" + EOL );
1244    }
1245
1246    /**
1247     * {@inheritDoc}
1248     */
1249    public void nonBreakingSpace()
1250    {
1251        markup( "~" );
1252    }
1253
1254    /** {@inheritDoc} */
1255    public void text( String text )
1256    {
1257        text( text, null );
1258    }
1259
1260    /** {@inheritDoc} */
1261    public void text( String text, SinkEventAttributes attributes )
1262    {
1263        if ( ignoreText )
1264        {
1265            return;
1266        }
1267        if ( isTitle )
1268        {
1269            title = text;
1270        }
1271        else if ( verbatimFlag )
1272        {
1273            verbatimContent( text );
1274        }
1275        else
1276        {
1277            content( text );
1278        }
1279    }
1280
1281    /** {@inheritDoc} */
1282    public void rawText( String text )
1283    {
1284        verbatimContent( text );
1285    }
1286
1287    /** {@inheritDoc} */
1288    public void comment( String comment )
1289    {
1290        rawText( EOL + "%" + comment );
1291    }
1292
1293    /**
1294     * {@inheritDoc}
1295     *
1296     * Unkown events just log a warning message but are ignored otherwise.
1297     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
1298     */
1299    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1300    {
1301        getLog().warn( "[Latex Sink] Unknown Sink event: '" + name + "', ignoring!" );
1302    }
1303
1304    // -----------------------------------------------------------------------
1305
1306    /**
1307     * Writes the text, preserving whitespace.
1308     *
1309     * @param text the text to write.
1310     */
1311    protected void markup( String text )
1312    {
1313        if ( text != null )
1314        {
1315            out.write( text, /*preserveSpace*/ true );
1316        }
1317    }
1318
1319    /**
1320     * Writes the text, without preserving whitespace.
1321     *
1322     * @param text the text to write.
1323     */
1324    protected void content( String text )
1325    {
1326        out.write( escaped( text ), /*preserveSpace*/ false );
1327    }
1328
1329    /**
1330     * Writes the text, preserving whitespace.
1331     *
1332     * @param text the text to write.
1333     */
1334    protected void verbatimContent( String text )
1335    {
1336        out.write( text, /*preserveSpace*/ true );
1337    }
1338
1339    // -----------------------------------------------------------------------
1340
1341    /**
1342     * Escapes special characters.
1343     *
1344     * @param text The text to escape.
1345     * @return The text with special characters replaced.
1346     */
1347    public static String escaped( String text )
1348    {
1349        int length = text.length();
1350        StringBuilder buffer = new StringBuilder( length );
1351
1352        for ( int i = 0; i < length; ++i )
1353        {
1354            char c = text.charAt( i );
1355            switch ( c )
1356            {
1357                case '-':
1358                case '<':
1359                case '>':
1360                    buffer.append( "\\symbol{" ).append( (int) c ).append( "}" );
1361                    break;
1362                case '~':
1363                    buffer.append( "\\textasciitilde " );
1364                    break;
1365                case '^':
1366                    buffer.append( "\\textasciicircum " );
1367                    break;
1368                case '|':
1369                    buffer.append( "\\textbar " );
1370                    break;
1371                case '\\':
1372                    buffer.append( "\\textbackslash " );
1373                    break;
1374                case '$':
1375                    buffer.append( "\\$" );
1376                    break;
1377                case '&':
1378                    buffer.append( "\\&" );
1379                    break;
1380                case '%':
1381                    buffer.append( "\\%" );
1382                    break;
1383                case '#':
1384                    buffer.append( "\\#" );
1385                    break;
1386                case '{':
1387                    buffer.append( "\\{" );
1388                    break;
1389                case '}':
1390                    buffer.append( "\\}" );
1391                    break;
1392                case '_':
1393                    buffer.append( "\\_" );
1394                    break;
1395                default:
1396                    buffer.append( c );
1397            }
1398        }
1399
1400        return buffer.toString();
1401    }
1402
1403    // ----------------------------------------------------------------------
1404    //
1405    // ----------------------------------------------------------------------
1406
1407    /**
1408     * {@inheritDoc}
1409     */
1410    public void flush()
1411    {
1412        out.flush();
1413    }
1414
1415    /**
1416     * {@inheritDoc}
1417     */
1418    public void close()
1419    {
1420        out.close();
1421
1422        init();
1423    }
1424
1425    // ----------------------------------------------------------------------
1426    //
1427    // ----------------------------------------------------------------------
1428
1429    /**
1430     * Returns the default sink commands from a resource.
1431     *
1432     * @throws java.io.IOException if the resource file cannot be read.
1433     * @return InputStream
1434     */
1435    private static InputStream getDefaultSinkCommands()
1436        throws IOException
1437    {
1438        return LatexSink.class.getResource( "default_sink_commands.tex" ).openStream();
1439    }
1440
1441    /**
1442     * Returns the default preamble from a resource.
1443     *
1444     * @return InputStream
1445     * @throws java.io.IOException if the resource file cannot be read.
1446     */
1447    private static InputStream getDefaultPreamble()
1448        throws IOException
1449    {
1450        return LatexSink.class.getResource( "default_preamble.tex" ).openStream();
1451    }
1452
1453    /**
1454     * Returns the default sink commands.
1455     *
1456     * @return String.
1457     */
1458    protected String defaultSinkCommands()
1459    {
1460        try
1461        {
1462            return IOUtil.toString( getDefaultSinkCommands() );
1463        }
1464        catch ( IOException ioe )
1465        {
1466            // this should not happen
1467            getLog().warn( "Could not read default LaTeX commands, the generated LaTeX file will not compile!" );
1468            getLog().debug( ioe );
1469
1470            return "";
1471        }
1472    }
1473
1474    /**
1475     * Returns the default preamble.
1476     *
1477     * @return String.
1478     */
1479    protected String defaultPreamble()
1480    {
1481        try
1482        {
1483            return IOUtil.toString( getDefaultPreamble() );
1484        }
1485        catch ( IOException ioe )
1486        {
1487            // this should not happen
1488            getLog().warn( "Could not read default LaTeX preamble, the generated LaTeX file will not compile!" );
1489            getLog().debug( ioe );
1490
1491            return "";
1492        }
1493    }
1494
1495    /**
1496     * {@inheritDoc}
1497     */
1498    protected void init()
1499    {
1500        super.init();
1501
1502        this.ignoreText = false;
1503        this.titleFlag = false;
1504        this.numberedListNesting = 0;
1505        this.verbatimFlag = false;
1506        this.figureFlag = false;
1507        this.tableFlag = false;
1508        this.gridFlag = false;
1509        this.cellJustif = null;
1510        this.cellCount = 0;
1511        this.isTitle = false;
1512        this.title = null;
1513    }
1514}