001package org.apache.maven.doxia.module.fo;
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.File;
023import java.io.IOException;
024import java.io.PrintWriter;
025import java.io.StringWriter;
026import java.io.Writer;
027import java.util.Enumeration;
028import java.util.HashMap;
029import java.util.LinkedList;
030import java.util.Map;
031import java.util.Set;
032import java.util.Stack;
033import java.util.TreeSet;
034
035import javax.swing.text.MutableAttributeSet;
036import javax.swing.text.html.HTML.Attribute;
037import javax.swing.text.html.HTML.Tag;
038
039import org.apache.maven.doxia.sink.AbstractXmlSink;
040import org.apache.maven.doxia.sink.SinkEventAttributeSet;
041import org.apache.maven.doxia.sink.SinkEventAttributes;
042import org.apache.maven.doxia.sink.SinkUtils;
043import org.apache.maven.doxia.util.DoxiaUtils;
044import org.apache.maven.doxia.util.HtmlTools;
045
046import org.codehaus.plexus.util.StringUtils;
047import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
048
049/**
050 * A Doxia Sink that produces a FO model. The usage is similar to the following:
051 *
052 * <pre>
053 * FoSink sink = new FoSink( writer );
054 * sink.beginDocument();
055 * ...
056 * sink.endDocument();
057 * </pre>
058 *
059 * @author ltheussl
060 * @version $Id$
061 * @since 1.1
062 */
063public class FoSink
064    extends AbstractXmlSink
065    implements FoMarkup
066{
067    /** For writing the result. */
068    private final PrintWriter out;
069
070    /** Used to get the current position in numbered lists. */
071    private final Stack<NumberedListItem> listStack;
072
073    /** Used to get attributes for a given FO element. */
074    private final FoConfiguration config;
075
076    /** Counts the current section level. */
077    private int section = 0;
078
079    /** Counts the current subsection level. */
080    private int subsection = 0;
081
082    /** Counts the current subsubsection level. */
083    private int subsubsection = 0;
084
085    /** Verbatim flag. */
086    private boolean verbatim;
087
088    /** figure flag. */
089    private boolean inFigure;
090
091    private final String encoding;
092
093    private final String languageId;
094
095    /** Stack of drawing borders on table cells. */
096    private final LinkedList<Boolean> tableGridStack;
097
098    /** Stack of alignment int[] of table cells. */
099    private final LinkedList<int[]> cellJustifStack;
100
101    /** Stack of justification of table cells. */
102    private final LinkedList<Boolean> isCellJustifStack;
103
104    /** Stack of current table cell. */
105    private final LinkedList<Integer> cellCountStack;
106
107    /** The stack of StringWriter to write the table result temporary, so we could play with the output and fix fo. */
108    private final LinkedList<StringWriter> tableContentWriterStack;
109
110    private final LinkedList<StringWriter> tableCaptionWriterStack;
111
112    private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
113
114    /** The stack of table caption */
115    private final LinkedList<String> tableCaptionStack;
116
117    /** Map of warn messages with a String as key to describe the error type and a Set as value.
118     * Using to reduce warn messages. */
119    protected Map<String, Set<String>> warnMessages;
120
121    /**
122     * Constructor, initialize the Writer.
123     *
124     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
125     * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
126     */
127    protected FoSink( Writer writer )
128    {
129        this( writer, "UTF-8" );
130    }
131
132    /**
133     * Constructor, initialize the Writer and tells which encoding is used.
134     *
135     * @param writer not null writer to write the result.
136     * @param encoding the encoding used, that should be written to the generated HTML content
137     * if not <code>null</code>.
138     */
139    protected FoSink( Writer writer, String encoding )
140    {
141        this( writer, encoding, null );
142    }
143
144    /**
145     * Constructor, initialize the Writer and tells which encoding and languageId are used.
146     *
147     * @param writer not null writer to write the result.
148     * @param encoding the encoding used, that should be written to the generated HTML content
149     * if not <code>null</code>.
150     * @param languageId language identifier for the root element as defined by
151     * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
152     * in addition, the empty string may be specified.
153     */
154    protected FoSink( Writer writer, String encoding, String languageId )
155    {
156        if ( writer == null )
157        {
158            throw new NullPointerException( "Null writer in FO Sink!" );
159        }
160
161        this.out = new PrintWriter( writer );
162        this.encoding = encoding;
163        this.languageId = languageId;
164        this.config = new FoConfiguration();
165
166        this.listStack = new Stack<NumberedListItem>();
167        this.tableGridStack = new LinkedList<Boolean>();
168        this.cellJustifStack = new LinkedList<int[]>();
169        this.isCellJustifStack = new LinkedList<Boolean>();
170        this.cellCountStack = new LinkedList<Integer>();
171        this.tableContentWriterStack = new LinkedList<StringWriter>();
172        this.tableCaptionWriterStack = new LinkedList<StringWriter>();
173        this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>();
174        this.tableCaptionStack = new LinkedList<String>();
175
176        setNameSpace( "fo" );
177    }
178
179    // TODO add FOP compliance mode?
180
181    /**
182     * Load configuration parameters from a File.
183     *
184     * @param configFile the configuration file.
185     *
186     * @throws java.io.IOException if the File cannot be read
187     *  or some error occurs when initializing the configuration parameters.
188     *
189     * @since 1.1.1
190     */
191    public void load( File configFile )
192            throws IOException
193    {
194        config.load( configFile );
195    }
196
197    /** {@inheritDoc} */
198    public void head( SinkEventAttributes attributes )
199    {
200        init();
201
202        startPageSequence( "0", null, null );
203    }
204
205    /** {@inheritDoc} */
206    public void head()
207    {
208        head( null );
209    }
210
211    /** {@inheritDoc} */
212    public void head_()
213    {
214        writeEOL();
215    }
216
217    /** {@inheritDoc} */
218    public void title( SinkEventAttributes attributes )
219    {
220        writeStartTag( BLOCK_TAG, "doc.header.title" );
221    }
222
223    /** {@inheritDoc} */
224    public void title()
225    {
226        title( null );
227    }
228
229    /** {@inheritDoc} */
230    public void title_()
231    {
232        writeEndTag( BLOCK_TAG );
233        writeEOL();
234    }
235
236    /** {@inheritDoc} */
237    public void author( SinkEventAttributes attributes )
238    {
239        writeStartTag( BLOCK_TAG, "doc.header.author" );
240    }
241
242    /** {@inheritDoc} */
243    public void author()
244    {
245        author( null );
246    }
247
248    /** {@inheritDoc} */
249    public void author_()
250    {
251        writeEndTag( BLOCK_TAG );
252        writeEOL();
253    }
254
255    /** {@inheritDoc} */
256    public void date( SinkEventAttributes attributes )
257    {
258        writeStartTag( BLOCK_TAG, "doc.header.date" );
259    }
260
261    /** {@inheritDoc} */
262    public void date()
263    {
264        date( null );
265    }
266
267    /** {@inheritDoc} */
268    public void date_()
269    {
270        writeEndTag( BLOCK_TAG );
271        writeEOL();
272    }
273
274    /** {@inheritDoc} */
275    public void body( SinkEventAttributes attributes )
276    {
277        // noop
278    }
279
280    /** {@inheritDoc} */
281    public void body()
282    {
283        body( null );
284    }
285
286    /** {@inheritDoc} */
287    public void body_()
288    {
289        writeEOL();
290        writeEndTag( FLOW_TAG );
291        writeEOL();
292        writeEndTag( PAGE_SEQUENCE_TAG );
293        writeEOL();
294        endDocument();
295    }
296
297    // -----------------------------------------------------------------------
298    //
299    // -----------------------------------------------------------------------
300
301    /** {@inheritDoc} */
302    public void sectionTitle()
303    {
304        // nop
305    }
306
307    /** {@inheritDoc} */
308    public void sectionTitle_()
309    {
310        // nop
311    }
312
313    /** {@inheritDoc} */
314    public void section( int level, SinkEventAttributes attributes )
315    {
316        if ( level == SECTION_LEVEL_1 )
317        {
318            section++;
319            subsection = 0;
320            subsubsection = 0;
321        }
322        else if ( level == SECTION_LEVEL_2 )
323        {
324            subsection++;
325            subsubsection = 0;
326        }
327        else if ( level == SECTION_LEVEL_3 )
328        {
329            subsubsection++;
330        }
331
332        onSection();
333    }
334
335    /** {@inheritDoc} */
336    public void section_( int level )
337    {
338        onSection_();
339    }
340
341    /** {@inheritDoc} */
342    public void sectionTitle( int level, SinkEventAttributes attributes )
343    {
344        onSectionTitle( level );
345    }
346
347    /** {@inheritDoc} */
348    public void sectionTitle_( int level )
349    {
350        onSectionTitle_();
351    }
352
353    /** {@inheritDoc} */
354    public void section1()
355    {
356        section( SECTION_LEVEL_1, null );
357    }
358
359    /** {@inheritDoc} */
360    public void sectionTitle1()
361    {
362        sectionTitle( SECTION_LEVEL_1, null );
363    }
364
365    /** {@inheritDoc} */
366    public void sectionTitle1_()
367    {
368        sectionTitle_( SECTION_LEVEL_1 );
369    }
370
371    /** {@inheritDoc} */
372    public void section1_()
373    {
374        section_( SECTION_LEVEL_1 );
375    }
376
377    /** {@inheritDoc} */
378    public void section2()
379    {
380        section( SECTION_LEVEL_2, null );
381    }
382
383    /** {@inheritDoc} */
384    public void sectionTitle2()
385    {
386        sectionTitle( SECTION_LEVEL_2, null );
387    }
388
389    /** {@inheritDoc} */
390    public void sectionTitle2_()
391    {
392        sectionTitle_( SECTION_LEVEL_2 );
393    }
394
395    /** {@inheritDoc} */
396    public void section2_()
397    {
398        section_( SECTION_LEVEL_2 );
399    }
400
401    /** {@inheritDoc} */
402    public void section3()
403    {
404        section( SECTION_LEVEL_3, null );
405    }
406
407    /** {@inheritDoc} */
408    public void sectionTitle3()
409    {
410        sectionTitle( SECTION_LEVEL_3, null );
411    }
412
413    /** {@inheritDoc} */
414    public void sectionTitle3_()
415    {
416        sectionTitle_( SECTION_LEVEL_3 );
417    }
418
419    /** {@inheritDoc} */
420    public void section3_()
421    {
422        section_( SECTION_LEVEL_3 );
423    }
424
425    /** {@inheritDoc} */
426    public void section4()
427    {
428        section( SECTION_LEVEL_4, null );
429    }
430
431    /** {@inheritDoc} */
432    public void sectionTitle4()
433    {
434        sectionTitle( SECTION_LEVEL_4, null );
435    }
436
437    /** {@inheritDoc} */
438    public void sectionTitle4_()
439    {
440        sectionTitle_( SECTION_LEVEL_4 );
441    }
442
443    /** {@inheritDoc} */
444    public void section4_()
445    {
446        section_( SECTION_LEVEL_4 );
447    }
448
449    /** {@inheritDoc} */
450    public void section5()
451    {
452        section( SECTION_LEVEL_5, null );
453    }
454
455    /** {@inheritDoc} */
456    public void sectionTitle5()
457    {
458        sectionTitle( SECTION_LEVEL_5, null );
459    }
460
461    /** {@inheritDoc} */
462    public void sectionTitle5_()
463    {
464        sectionTitle_( SECTION_LEVEL_5 );
465    }
466
467    /** {@inheritDoc} */
468    public void section5_()
469    {
470        section_( SECTION_LEVEL_5 );
471    }
472
473    /** Starts a section/subsection. */
474    private void onSection()
475    {
476        writeEOL();
477        writeStartTag( BLOCK_TAG, "body.text" );
478    }
479
480    /**
481     * Starts a section title.
482     *
483     * @param depth The section level.
484     */
485    private void onSectionTitle( int depth )
486    {
487        StringBuilder title = new StringBuilder( 16 );
488
489        title.append( getChapterString() );
490
491        writeEOL();
492        if ( depth == SECTION_LEVEL_1 )
493        {
494            writeStartTag( BLOCK_TAG, "body.h1" );
495            title.append( section ).append( "   " );
496        }
497        else if ( depth == SECTION_LEVEL_2 )
498        {
499            writeStartTag( BLOCK_TAG, "body.h2" );
500            title.append( section ).append( "." );
501            title.append( subsection ).append( "   " );
502        }
503        else if ( depth == SECTION_LEVEL_3 )
504        {
505            writeStartTag( BLOCK_TAG, "body.h3" );
506            title.append( section ).append( "." );
507            title.append( subsection ).append( "." );
508            title.append( subsubsection ).append( "   " );
509        }
510        else if ( depth == SECTION_LEVEL_4 )
511        {
512            writeStartTag( BLOCK_TAG, "body.h4" );
513        }
514        else
515        {
516            writeStartTag( BLOCK_TAG, "body.h5" );
517        }
518
519        write( title.toString() );
520    }
521
522    /** Ends a section title. */
523    private void onSectionTitle_()
524    {
525        writeEndTag( BLOCK_TAG );
526        writeEOL();
527    }
528
529    /** Ends a section/subsection. */
530    private void onSection_()
531    {
532        writeEndTag( BLOCK_TAG );
533        writeEOL();
534    }
535
536    /**
537     * Resets the section counter to 0.
538     * Only useful for overriding classes, like AggregateSink, the FoSink puts everything into one chapter.
539     */
540    protected void resetSectionCounter()
541    {
542        this.section = 0;
543    }
544
545    /**
546     * Returns the current chapter number as a string.
547     * By default does nothing, gets overridden by AggregateSink.
548     *
549     * @return an empty String.
550     */
551    protected String getChapterString()
552    {
553        return "";
554    }
555
556    // -----------------------------------------------------------------------
557    //
558    // -----------------------------------------------------------------------
559
560    /** {@inheritDoc} */
561    public void list( SinkEventAttributes attributes )
562    {
563        writeEOL();
564        writeStartTag( LIST_BLOCK_TAG, "list" );
565    }
566
567    /** {@inheritDoc} */
568    public void list()
569    {
570        list( null );
571    }
572
573    /** {@inheritDoc} */
574    public void list_()
575    {
576        writeEndTag( LIST_BLOCK_TAG );
577        writeEOL();
578    }
579
580    /** {@inheritDoc} */
581    public void listItem( SinkEventAttributes attributes )
582    {
583        writeStartTag( LIST_ITEM_TAG, "list.item" );
584        writeStartTag( LIST_ITEM_LABEL_TAG );
585        writeStartTag( BLOCK_TAG );
586        write( "&#8226;" ); // TODO customize?
587        writeEndTag( BLOCK_TAG );
588        writeEndTag( LIST_ITEM_LABEL_TAG );
589        writeEOL();
590        writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
591        writeEOL();
592        writeStartTag( BLOCK_TAG );
593    }
594
595    /** {@inheritDoc} */
596    public void listItem()
597    {
598        listItem( null );
599    }
600
601    /** {@inheritDoc} */
602    public void listItem_()
603    {
604        writeEndTag( BLOCK_TAG );
605        writeEOL();
606        writeEndTag( LIST_ITEM_BODY_TAG );
607        writeEOL();
608        writeEndTag( LIST_ITEM_TAG );
609        writeEOL();
610    }
611
612    /** {@inheritDoc} */
613    public void numberedList( int numbering, SinkEventAttributes attributes )
614    {
615        this.listStack.push( new NumberedListItem( numbering ) );
616        writeEOL();
617        writeStartTag( LIST_BLOCK_TAG, "list" );
618    }
619
620    /** {@inheritDoc} */
621    public void numberedList( int numbering )
622    {
623        numberedList( numbering, null );
624    }
625
626    /** {@inheritDoc} */
627    public void numberedList_()
628    {
629        this.listStack.pop();
630        writeEndTag( LIST_BLOCK_TAG );
631        writeEOL();
632    }
633
634    /** {@inheritDoc} */
635    public void numberedListItem( SinkEventAttributes attributes )
636    {
637        NumberedListItem current = this.listStack.peek();
638        current.next();
639
640        writeStartTag( LIST_ITEM_TAG, "list.item" );
641
642        writeEOL();
643        writeStartTag( LIST_ITEM_LABEL_TAG );
644        writeEOL();
645        writeStartTag( BLOCK_TAG );
646        write( current.getListItemSymbol() );
647        writeEndTag( BLOCK_TAG );
648        writeEOL();
649        writeEndTag( LIST_ITEM_LABEL_TAG );
650        writeEOL();
651
652        writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
653        writeEOL();
654        writeStartTag( BLOCK_TAG );
655    }
656
657    /** {@inheritDoc} */
658    public void numberedListItem()
659    {
660        numberedListItem( null );
661    }
662
663    /** {@inheritDoc} */
664    public void numberedListItem_()
665    {
666        writeEndTag( BLOCK_TAG );
667        writeEOL();
668        writeEndTag( LIST_ITEM_BODY_TAG );
669        writeEOL();
670        writeEndTag( LIST_ITEM_TAG );
671        writeEOL();
672    }
673
674    /** {@inheritDoc} */
675    public void definitionList( SinkEventAttributes attributes )
676    {
677        writeEOL();
678        writeStartTag( BLOCK_TAG, "dl" );
679    }
680
681    /** {@inheritDoc} */
682    public void definitionList()
683    {
684        definitionList( null );
685    }
686
687    /** {@inheritDoc} */
688    public void definitionList_()
689    {
690        writeEndTag( BLOCK_TAG );
691        writeEOL();
692    }
693
694    /** {@inheritDoc} */
695    public void definitionListItem( SinkEventAttributes attributes )
696    {
697        // nop
698    }
699
700    /** {@inheritDoc} */
701    public void definitionListItem()
702    {
703        definitionListItem( null );
704    }
705
706    /** {@inheritDoc} */
707    public void definitionListItem_()
708    {
709        // nop
710    }
711
712    /** {@inheritDoc} */
713    public void definedTerm( SinkEventAttributes attributes )
714    {
715        writeStartTag( BLOCK_TAG, "dt" );
716    }
717
718    /** {@inheritDoc} */
719    public void definedTerm()
720    {
721        definedTerm( null );
722    }
723
724    /** {@inheritDoc} */
725    public void definedTerm_()
726    {
727        writeEndTag( BLOCK_TAG );
728        writeEOL();
729    }
730
731    /** {@inheritDoc} */
732    public void definition( SinkEventAttributes attributes )
733    {
734        writeEOL();
735        writeStartTag( BLOCK_TAG, "dd" );
736    }
737
738    /** {@inheritDoc} */
739    public void definition()
740    {
741        definition( null );
742    }
743
744    /** {@inheritDoc} */
745    public void definition_()
746    {
747        writeEndTag( BLOCK_TAG );
748        writeEOL();
749    }
750
751    /** {@inheritDoc} */
752    public void figure( SinkEventAttributes attributes )
753    {
754        this.inFigure = true;
755        writeEOL();
756        writeStartTag( BLOCK_TAG, "figure.display" );
757    }
758
759    /** {@inheritDoc} */
760    public void figure()
761    {
762        figure( null );
763    }
764
765    /** {@inheritDoc} */
766    public void figure_()
767    {
768        this.inFigure = false;
769        writeEndTag( BLOCK_TAG );
770        writeEOL();
771    }
772
773    /** {@inheritDoc} */
774    public void figureGraphics( String name )
775    {
776        figureGraphics( name, null );
777    }
778
779    /** {@inheritDoc} */
780    public void figureGraphics( String src, SinkEventAttributes attributes )
781    {
782        MutableAttributeSet atts = config.getAttributeSet( "figure.graphics" );
783        atts.addAttribute( Attribute.SRC.toString(), src );
784
785        // http://xmlgraphics.apache.org/fop/graphics.html#resolution
786
787        final String[] valids = new String[] {"content-height", "content-width", "height", "width"};
788        final MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, valids );
789
790        if ( filtered != null )
791        {
792            atts.addAttributes( filtered );
793        }
794
795        writeln( "<fo:external-graphic" + SinkUtils.getAttributeString( atts ) + "/>" );
796    }
797
798    /**
799     * Flags if we are inside a figure.
800     *
801     * @return True if we are between {@link #figure()} and {@link #figure_()} calls.
802     */
803    protected boolean isFigure()
804    {
805        return this.inFigure;
806    }
807
808    /** {@inheritDoc} */
809    public void figureCaption( SinkEventAttributes attributes )
810    {
811        writeStartTag( BLOCK_TAG, "figure.caption" );
812    }
813
814    /** {@inheritDoc} */
815    public void figureCaption()
816    {
817        figureCaption( null );
818    }
819
820    /** {@inheritDoc} */
821    public void figureCaption_()
822    {
823        writeEndTag( BLOCK_TAG );
824        writeEOL();
825    }
826
827    /** {@inheritDoc} */
828    public void paragraph()
829    {
830        paragraph( null );
831    }
832
833    /** {@inheritDoc} */
834    public void paragraph( SinkEventAttributes attributes )
835    {
836        MutableAttributeSet atts = config.getAttributeSet( "normal.paragraph" );
837
838        if ( attributes != null && attributes.isDefined( SinkEventAttributes.ALIGN ) )
839        {
840            atts.addAttribute( "text-align", attributes.getAttribute( SinkEventAttributes.ALIGN ) );
841        }
842
843        writeEOL();
844        writeStartTag( BLOCK_TAG, atts );
845    }
846
847    /** {@inheritDoc} */
848    public void paragraph_()
849    {
850        writeEndTag( BLOCK_TAG );
851        writeEOL();
852    }
853
854    /** {@inheritDoc} */
855    public void verbatim( SinkEventAttributes attributes )
856    {
857        this.verbatim = true;
858
859        boolean boxed = false;
860
861        if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) )
862        {
863            boxed =
864                "boxed".equals( attributes.getAttribute( SinkEventAttributes.DECORATION ).toString() );
865        }
866
867        if ( boxed )
868        {
869            writeStartTag( BLOCK_TAG, "body.source" );
870        }
871        else
872        {
873            writeStartTag( BLOCK_TAG, "body.pre" );
874        }
875    }
876
877    /** {@inheritDoc} */
878    public void verbatim( boolean boxed )
879    {
880        verbatim( boxed ? SinkEventAttributeSet.BOXED : null );
881    }
882
883    /** {@inheritDoc} */
884    public void verbatim_()
885    {
886        this.verbatim = false;
887        writeEndTag( BLOCK_TAG );
888        writeEOL();
889    }
890
891    /** {@inheritDoc} */
892    public void horizontalRule( SinkEventAttributes attributes )
893    {
894        writeEOL();
895        writeEOL();
896        writeStartTag( BLOCK_TAG );
897        writeEmptyTag( LEADER_TAG, "body.rule" );
898        writeEndTag( BLOCK_TAG );
899        writeEOL();
900    }
901
902    /** {@inheritDoc} */
903    public void horizontalRule()
904    {
905        horizontalRule( null );
906    }
907
908    /** {@inheritDoc} */
909    public void pageBreak()
910    {
911        writeEmptyTag( BLOCK_TAG, "break-before", "page" );
912        writeEOL();
913    }
914
915    /** {@inheritDoc} */
916    public void table( SinkEventAttributes attributes )
917    {
918        writeEOL();
919        writeStartTag( BLOCK_TAG, "table.padding" );
920
921        // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95
922        //writeStartTag( TABLE_AND_CAPTION_TAG );
923
924        this.tableContentWriterStack.addLast( new StringWriter() );
925        writeStartTag( TABLE_TAG, "table.layout" );
926    }
927
928    /** {@inheritDoc} */
929    public void table()
930    {
931        table( null );
932    }
933
934    /** {@inheritDoc} */
935    public void table_()
936    {
937        String content = this.tableContentWriterStack.removeLast().toString();
938
939        StringBuilder sb = new StringBuilder();
940        int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
941        for ( int i = 0; i < cellCount; i++ )
942        {
943            sb.append( "<fo:table-column column-width=\"proportional-column-width(1)\"/>" );
944            sb.append( EOL );
945        }
946
947        int index = content.indexOf( ">" ) + 1;
948        writeln( content.substring( 0, index ) );
949        write( sb.toString() );
950        write( content.substring( index ) );
951
952        writeEndTag( TABLE_TAG );
953        writeEOL();
954
955        // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95
956        //writeEndTag( TABLE_AND_CAPTION_TAG );
957
958        writeEndTag( BLOCK_TAG );
959        writeEOL();
960
961        if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
962        {
963            paragraph( SinkEventAttributeSet.CENTER );
964            write( this.tableCaptionStack.removeLast().toString() );
965            paragraph_();
966        }
967    }
968
969    /** {@inheritDoc} */
970    public void tableRows( int[] justification, boolean grid )
971    {
972        this.tableGridStack.addLast( Boolean.valueOf( grid ) );
973        this.cellJustifStack.addLast( justification );
974        this.isCellJustifStack.addLast( Boolean.valueOf( true ) );
975        this.cellCountStack.addLast( Integer.valueOf( 0 ) );
976        writeEOL();
977        writeStartTag( TABLE_BODY_TAG );
978    }
979
980    /** {@inheritDoc} */
981    public void tableRows_()
982    {
983        this.tableGridStack.removeLast();
984        this.cellJustifStack.removeLast();
985        this.isCellJustifStack.removeLast();
986        writeEndTag( TABLE_BODY_TAG );
987        writeEOL();
988    }
989
990    /** {@inheritDoc} */
991    public void tableRow( SinkEventAttributes attributes )
992    {
993        // TODO spacer rows
994        writeStartTag( TABLE_ROW_TAG, "table.body.row" );
995        this.cellCountStack.removeLast();
996        this.cellCountStack.addLast( Integer.valueOf( 0 ) );
997    }
998
999    /** {@inheritDoc} */
1000    public void tableRow()
1001    {
1002        tableRow( null );
1003    }
1004
1005    /** {@inheritDoc} */
1006    public void tableRow_()
1007    {
1008        writeEndTag( TABLE_ROW_TAG );
1009        writeEOL();
1010    }
1011
1012    /** {@inheritDoc} */
1013    public void tableCell( SinkEventAttributes attributes )
1014    {
1015        tableCell( false, attributes );
1016    }
1017
1018    /** {@inheritDoc} */
1019    public void tableCell()
1020    {
1021        tableCell( (SinkEventAttributes) null );
1022    }
1023
1024    /** {@inheritDoc} */
1025    public void tableCell( String width )
1026    {
1027        // TODO: fop can't handle cell width
1028        tableCell();
1029    }
1030
1031    /** {@inheritDoc} */
1032    public void tableHeaderCell( SinkEventAttributes attributes )
1033    {
1034        tableCell( true, attributes );
1035    }
1036
1037    /** {@inheritDoc} */
1038    public void tableHeaderCell()
1039    {
1040        tableHeaderCell( (SinkEventAttributes) null );
1041    }
1042
1043    /** {@inheritDoc} */
1044    public void tableHeaderCell( String width )
1045    {
1046        // TODO: fop can't handle cell width
1047        tableHeaderCell();
1048    }
1049
1050    /**
1051     * Writes a table cell.
1052     *
1053     * @param headerRow true if this is a header cell.
1054     * @param attributes the cell attributes, could be null.
1055     */
1056    private void tableCell( boolean headerRow, SinkEventAttributes attributes )
1057    {
1058        MutableAttributeSet cellAtts = headerRow
1059                 ? config.getAttributeSet( "table.heading.cell" )
1060                 : config.getAttributeSet( "table.body.cell" );
1061
1062        // the column-number is needed for the hack to center the table, see tableRows.
1063        int cellCount = Integer.parseInt( this.cellCountStack.getLast().toString() );
1064        cellAtts.addAttribute( "column-number", String.valueOf( cellCount + 1 ) );
1065
1066        if ( this.tableGridStack.getLast().equals( Boolean.TRUE ) )
1067        {
1068            cellAtts.addAttributes( config.getAttributeSet( "table.body.cell.grid" ) );
1069        }
1070
1071        MutableAttributeSet blockAtts = headerRow
1072                 ? config.getAttributeSet( "table.heading.block" )
1073                 : config.getAttributeSet( "table.body.block" );
1074
1075        String justif = null;
1076        if ( attributes == null )
1077        {
1078            attributes = new SinkEventAttributeSet( 0 );
1079        }
1080
1081        if ( attributes.isDefined( Attribute.ALIGN.toString() ) )
1082        {
1083            justif = attributes.getAttribute( Attribute.ALIGN.toString() ).toString();
1084        }
1085
1086        int[] cellJustif = this.cellJustifStack.getLast();
1087        if ( justif == null && cellJustif != null && cellJustif.length > 0
1088            && this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
1089        {
1090            switch ( cellJustif[Math.min( cellCount, cellJustif.length - 1 )] )
1091            {
1092                case JUSTIFY_LEFT:
1093                    justif = "left";
1094                    break;
1095                case JUSTIFY_RIGHT:
1096                    justif = "right";
1097                    break;
1098                case JUSTIFY_CENTER:
1099                default:
1100                    justif = "center";
1101            }
1102        }
1103
1104        if ( justif != null )
1105        {
1106            blockAtts.addAttribute( "text-align", justif );
1107        }
1108
1109        writeStartTag( TABLE_CELL_TAG, cellAtts );
1110        writeEOL();
1111        writeStartTag( BLOCK_TAG, blockAtts );
1112        writeEOL();
1113    }
1114
1115    /** {@inheritDoc} */
1116    public void tableCell_()
1117    {
1118        writeEndTag( BLOCK_TAG );
1119        writeEOL();
1120        writeEndTag( TABLE_CELL_TAG );
1121        writeEOL();
1122
1123        if ( this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
1124        {
1125            int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
1126            this.cellCountStack.addLast( Integer.valueOf( ++cellCount ) );
1127        }
1128    }
1129
1130    /** {@inheritDoc} */
1131    public void tableHeaderCell_()
1132    {
1133        tableCell_();
1134    }
1135
1136    /** {@inheritDoc} */
1137    public void tableCaption( SinkEventAttributes attributes )
1138    {
1139        StringWriter sw = new StringWriter();
1140        this.tableCaptionWriterStack.addLast( sw );
1141        this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
1142
1143        // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95
1144        //writeStartTag( TABLE_CAPTION_TAG );
1145
1146        // TODO: how to implement this otherwise?
1147        // table-footer doesn't work because it has to be declared before table-body.
1148    }
1149
1150    /** {@inheritDoc} */
1151    public void tableCaption()
1152    {
1153        tableCaption( null );
1154    }
1155
1156    /** {@inheritDoc} */
1157    public void tableCaption_()
1158    {
1159        if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1160        {
1161            this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
1162            this.tableCaptionXMLWriterStack.removeLast();
1163        }
1164        // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95
1165        //writeEndTag( TABLE_CAPTION_TAG );
1166    }
1167
1168    /** {@inheritDoc} */
1169    public void anchor( String name, SinkEventAttributes attributes )
1170    {
1171        if ( name == null )
1172        {
1173            throw new NullPointerException( "Anchor name cannot be null!" );
1174        }
1175
1176        String anchor = name;
1177
1178        if ( !DoxiaUtils.isValidId( anchor ) )
1179        {
1180            anchor = DoxiaUtils.encodeId( name, true );
1181
1182            String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
1183            logMessage( "modifiedLink", msg );
1184        }
1185
1186        anchor = "#" + name;
1187
1188        writeStartTag( INLINE_TAG, "id", anchor );
1189    }
1190
1191    /** {@inheritDoc} */
1192    public void anchor( String name )
1193    {
1194        anchor( name, null );
1195    }
1196
1197    /** {@inheritDoc} */
1198    public void anchor_()
1199    {
1200        writeEndTag( INLINE_TAG );
1201    }
1202
1203    /** {@inheritDoc} */
1204    public void link( String name, SinkEventAttributes attributes )
1205    {
1206        if ( name == null )
1207        {
1208            throw new NullPointerException( "Link name cannot be null!" );
1209        }
1210
1211        if ( DoxiaUtils.isExternalLink( name ) )
1212        {
1213            writeStartTag( BASIC_LINK_TAG, "external-destination", HtmlTools.escapeHTML( name ) );
1214            writeStartTag( INLINE_TAG, "href.external" );
1215        }
1216        else if ( DoxiaUtils.isInternalLink( name ) )
1217        {
1218            String anchor = name.substring( 1 );
1219
1220            if ( !DoxiaUtils.isValidId( anchor ) )
1221            {
1222                anchor = DoxiaUtils.encodeId( anchor, true );
1223
1224                String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
1225                logMessage( "modifiedLink", msg );
1226            }
1227
1228            anchor = "#" + anchor;
1229
1230            writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
1231            writeStartTag( INLINE_TAG, "href.internal" );
1232        }
1233        else
1234        {
1235            // treat everything else as is
1236            String anchor = name;
1237
1238            writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
1239            writeStartTag( INLINE_TAG, "href.internal" );
1240        }
1241    }
1242
1243    /** {@inheritDoc} */
1244    public void link( String name )
1245    {
1246        link( name, null );
1247    }
1248
1249    /** {@inheritDoc} */
1250    public void link_()
1251    {
1252        writeEndTag( INLINE_TAG );
1253        writeEndTag( BASIC_LINK_TAG );
1254    }
1255
1256    /** {@inheritDoc} */
1257    public void italic()
1258    {
1259        writeStartTag( INLINE_TAG, "italic" );
1260    }
1261
1262    /** {@inheritDoc} */
1263    public void italic_()
1264    {
1265        writeEndTag( INLINE_TAG );
1266    }
1267
1268    /** {@inheritDoc} */
1269    public void bold()
1270    {
1271        writeStartTag( INLINE_TAG, "bold" );
1272    }
1273
1274    /** {@inheritDoc} */
1275    public void bold_()
1276    {
1277        writeEndTag( INLINE_TAG );
1278    }
1279
1280    /** {@inheritDoc} */
1281    public void monospaced()
1282    {
1283        writeStartTag( INLINE_TAG, "monospace" );
1284    }
1285
1286    /** {@inheritDoc} */
1287    public void monospaced_()
1288    {
1289        writeEndTag( INLINE_TAG );
1290    }
1291
1292    /** {@inheritDoc} */
1293    public void lineBreak( SinkEventAttributes attributes )
1294    {
1295        writeEOL();
1296        writeEOL();
1297        writeSimpleTag( BLOCK_TAG );
1298    }
1299
1300    /** {@inheritDoc} */
1301    public void lineBreak()
1302    {
1303        lineBreak( null );
1304    }
1305
1306    /** {@inheritDoc} */
1307    public void nonBreakingSpace()
1308    {
1309        write( "&#160;" );
1310    }
1311
1312    /** {@inheritDoc} */
1313    public void text( String text, SinkEventAttributes attributes )
1314    {
1315        content( text );
1316    }
1317
1318    /** {@inheritDoc} */
1319    public void text( String text )
1320    {
1321        text( text, null );
1322    }
1323
1324    /** {@inheritDoc} */
1325    public void rawText( String text )
1326    {
1327        write( text );
1328    }
1329
1330    /** {@inheritDoc} */
1331    public void flush()
1332    {
1333        out.flush();
1334    }
1335
1336    /** {@inheritDoc} */
1337    public void close()
1338    {
1339        out.close();
1340
1341        if ( getLog().isWarnEnabled() && this.warnMessages != null )
1342        {
1343            for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
1344            {
1345                for ( String msg : entry.getValue() )
1346                {
1347                    getLog().warn( msg );
1348                }
1349            }
1350
1351            this.warnMessages = null;
1352        }
1353
1354        init();
1355    }
1356
1357    /**
1358     * {@inheritDoc}
1359     *
1360     * Unkown events just log a warning message but are ignored otherwise.
1361     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
1362     */
1363    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1364    {
1365        String msg = "Unknown Sink event: '" + name + "', ignoring!";
1366        logMessage( "unknownEvent", msg );
1367    }
1368
1369    /** {@inheritDoc} */
1370    public void comment( String comment )
1371    {
1372
1373        if ( StringUtils.isNotEmpty( comment ) && comment.indexOf( "--" ) != -1 )
1374        {
1375            String originalComment = comment;
1376            // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments
1377            while ( comment.indexOf( "--" ) != -1 )
1378            {
1379                comment = StringUtils.replace( comment, "--", "- -" );
1380            }
1381
1382            String msg = "Modified invalid comment: '" + originalComment + "' to '" + comment + "'";
1383            logMessage( "modifyComment", msg );
1384        }
1385
1386        StringBuilder buf = new StringBuilder( comment.length() + 9 );
1387
1388        buf.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS ).append( SPACE );
1389        buf.append( comment );
1390        buf.append( SPACE ).append( MINUS ).append( MINUS ).append( GREATER_THAN );
1391
1392        write( buf.toString() );
1393    }
1394
1395    /**
1396     * Writes the beginning of a FO document.
1397     */
1398    public void beginDocument()
1399    {
1400        write( "<?xml version=\"1.0\"" );
1401        if ( encoding != null )
1402        {
1403            write( " encoding=\"" + encoding + "\"" );
1404        }
1405        write( "?>" );
1406        writeEOL();
1407
1408        MutableAttributeSet atts = new SinkEventAttributeSet();
1409        atts.addAttribute( "xmlns:" + getNameSpace(), FO_NAMESPACE );
1410
1411        if ( languageId != null )
1412        {
1413            atts.addAttribute( "language", languageId );
1414        }
1415
1416        writeStartTag( ROOT_TAG, atts );
1417
1418        writeStartTag( LAYOUT_MASTER_SET_TAG );
1419
1420        writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.cover-page" );
1421        writeEmptyTag( REGION_BODY_TAG, "layout.master.set.cover-page.region-body" );
1422        writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1423        writeEOL();
1424
1425        writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.toc" );
1426        writeEmptyTag( REGION_BODY_TAG, "layout.master.set.toc.region-body" );
1427        writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.toc.region-before" );
1428        writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.toc.region-after" );
1429        writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1430        writeEOL();
1431
1432        writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.body" );
1433        writeEmptyTag( REGION_BODY_TAG, "layout.master.set.body.region-body" );
1434        writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.body.region-before" );
1435        writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.body.region-after" );
1436        writeEndTag( SIMPLE_PAGE_MASTER_TAG );
1437        writeEOL();
1438
1439        writeEndTag( LAYOUT_MASTER_SET_TAG );
1440        writeEOL();
1441
1442        pdfBookmarks();
1443    }
1444
1445    /**
1446     * Writes the end of a FO document, flushes and closes the stream.
1447     */
1448    public void endDocument()
1449    {
1450        writeEndTag( ROOT_TAG );
1451        writeEOL();
1452
1453        flush();
1454        close();
1455    }
1456
1457    // ----------------------------------------------------------------------
1458    //
1459    // ----------------------------------------------------------------------
1460
1461    /**
1462     * Returns the configuration object of this sink.
1463     *
1464     * @return The configuration object of this sink.
1465     */
1466    protected FoConfiguration getFoConfiguration()
1467    {
1468        return config;
1469    }
1470
1471    /**
1472     * Writes a start tag, prepending EOL.
1473     *
1474     * @param tag The tag.
1475     * @param attributeId An id identifying the attribute set.
1476     */
1477    protected void writeStartTag( Tag tag, String attributeId )
1478    {
1479        writeEOL();
1480        writeStartTag( tag, config.getAttributeSet( attributeId ) );
1481    }
1482
1483    /**
1484     * Writes a start tag, prepending EOL.
1485     *
1486     * @param tag The tag.
1487     * @param id An id to add.
1488     * @param name The name (value) of the id.
1489     */
1490    protected void writeStartTag( Tag tag, String id, String name )
1491    {
1492        writeEOL();
1493        MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
1494
1495        writeStartTag( tag, att );
1496    }
1497
1498    /**
1499     * Writes a start tag, prepending EOL.
1500     *
1501     * @param tag The tag.
1502     * @param id An id to add.
1503     * @param name The name (value) of the id.
1504     * @param attributeId An id identifying the attribute set.
1505     */
1506    protected void writeStartTag( Tag tag, String id, String name, String attributeId )
1507    {
1508        MutableAttributeSet att = config.getAttributeSet( attributeId );
1509
1510        // make sure we don't add it twice
1511        if ( att.isDefined( id ) )
1512        {
1513            att.removeAttribute( id );
1514        }
1515
1516        att.addAttribute( id, name );
1517
1518        writeEOL();
1519        writeStartTag( tag, att );
1520    }
1521
1522    /**
1523     * Writes an empty tag, prepending EOL.
1524     *
1525     * @param tag The tag.
1526     * @param id An id to add.
1527     * @param name The name (value) of the id.
1528     */
1529    protected void writeEmptyTag( Tag tag, String id, String name )
1530    {
1531        MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
1532
1533        writeEOL();
1534        writeSimpleTag( tag, att );
1535    }
1536
1537    /**
1538     * Writes a simple tag, appending EOL.
1539     *
1540     * @param tag The tag name.
1541     * @param attributeId An id identifying the attribute set.
1542     */
1543    protected void writeEmptyTag( Tag tag, String attributeId )
1544    {
1545        writeEOL();
1546        writeSimpleTag( tag, config.getAttributeSet( attributeId ) );
1547    }
1548
1549    /**
1550     * {@inheritDoc}
1551     *
1552     * Writes a text, swallowing any exceptions.
1553     */
1554    protected void write( String text )
1555    {
1556        if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1557        {
1558            this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) );
1559        }
1560        else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
1561        {
1562            this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
1563        }
1564        else
1565        {
1566            out.write( unifyEOLs( text ) );
1567        }
1568    }
1569
1570    /**
1571     * Writes a text, appending EOL.
1572     *
1573     * @param text The text to write.
1574     */
1575    protected void writeln( String text )
1576    {
1577        write( text );
1578        writeEOL();
1579    }
1580
1581    /**
1582     * Writes content, escaping special characters.
1583     *
1584     * @param text The text to write.
1585     */
1586    protected void content( String text )
1587    {
1588        write( escaped( text, verbatim ) );
1589    }
1590
1591    /**
1592     * Escapes special characters so that the text can be included in a fo file.
1593     *
1594     * @param text The text to process.
1595     * @param verb In verbatim mode, white space and newlines are escaped.
1596     * @return The text with special characters escaped.
1597     */
1598    public static String escaped( String text, boolean verb )
1599    {
1600        int length = text.length();
1601        StringBuilder buffer = new StringBuilder( length );
1602
1603        for ( int i = 0; i < length; ++i )
1604        {
1605            char c = text.charAt( i );
1606            switch ( c )
1607            {
1608                case ' ':
1609                    if ( verb )
1610                    {
1611                        buffer.append( "&#160;" );
1612                    }
1613                    else
1614                    {
1615                        buffer.append( c );
1616                    }
1617                    break;
1618                case '<':
1619                    buffer.append( "&lt;" );
1620                    break;
1621                case '>':
1622                    buffer.append( "&gt;" );
1623                    break;
1624                case '&':
1625                    buffer.append( "&amp;" );
1626                    break;
1627                case '\n':
1628                    buffer.append( EOL );
1629                    if ( verb )
1630                    {
1631                        buffer.append( "<fo:block/>" + EOL );
1632                    }
1633                    break;
1634                default:
1635                    if ( needsSymbolFont( c ) )
1636                    {
1637                        // TODO: make font configurable?
1638                        buffer.append( "<fo:inline font-family=\"Symbol\">" ).append( c ).append( "</fo:inline>" );
1639                    }
1640                    else
1641                    {
1642                        buffer.append( c );
1643                    }
1644            }
1645        }
1646
1647        return buffer.toString();
1648    }
1649
1650    /** {@inheritDoc} */
1651    protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
1652    {
1653        if ( this.tableCaptionXMLWriterStack.isEmpty() )
1654        {
1655            super.writeStartTag ( t, att, isSimpleTag );
1656        }
1657        else
1658        {
1659            String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
1660            this.tableCaptionXMLWriterStack.getLast().startElement( tag );
1661
1662            if ( att != null )
1663            {
1664                Enumeration<?> names = att.getAttributeNames();
1665                while ( names.hasMoreElements() )
1666                {
1667                    Object key = names.nextElement();
1668                    Object value = att.getAttribute( key );
1669
1670                    this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
1671                }
1672            }
1673
1674            if ( isSimpleTag )
1675            {
1676                this.tableCaptionXMLWriterStack.getLast().endElement();
1677            }
1678        }
1679    }
1680
1681    /** {@inheritDoc} */
1682    protected void writeEndTag( Tag t )
1683    {
1684        if ( this.tableCaptionXMLWriterStack.isEmpty() )
1685        {
1686            super.writeEndTag( t );
1687        }
1688        else
1689        {
1690            this.tableCaptionXMLWriterStack.getLast().endElement();
1691        }
1692    }
1693
1694    private static final char UPPER_ALPHA = 0x391;
1695    private static final char PIV = 0x3d6;
1696    private static final char OLINE = 0x203e;
1697    private static final char DIAMS = 0x2666;
1698    private static final char EURO = 0x20ac;
1699    private static final char TRADE = 0x2122;
1700    private static final char PRIME = 0x2032;
1701    private static final char PPRIME = 0x2033;
1702
1703    private static boolean needsSymbolFont( char c )
1704    {
1705        // greek characters and mathematical symbols, except the euro and trade symbols
1706        // symbols I couldn't get to display in any font:
1707        // zwnj (0x200C), zwj (0x200D), lrm (0x200E), rlm (0x200F), oline (0x203E),
1708        // lceil (0x2038), rceil (0x2039), lfloor (0x203A), rfloor (0x203B)
1709        return ( c >= UPPER_ALPHA && c <= PIV )
1710                || ( c == PRIME || c == PPRIME )
1711                || ( c >= OLINE && c <= DIAMS && c != EURO && c != TRADE );
1712    }
1713
1714    /**
1715     * Starts a page sequence.
1716     *
1717     * @param initPageNumber The initial page number. Should be either "0" (for the first page) or "auto".
1718     * @param headerText The text to write in the header, if null, nothing is written.
1719     * @param footerText The text to write in the footer, if null, nothing is written.
1720     */
1721    protected void startPageSequence( String initPageNumber, String headerText, String footerText )
1722    {
1723        writeln( "<fo:page-sequence initial-page-number=\"" + initPageNumber + "\" master-reference=\"body\">" );
1724        regionBefore( headerText );
1725        regionAfter( footerText );
1726        writeln( "<fo:flow flow-name=\"xsl-region-body\">" );
1727        chapterHeading( null, true );
1728    }
1729
1730    /**
1731     * Writes a 'xsl-region-before' block.
1732     *
1733     * @param headerText The text to write in the header, if null, nothing is written.
1734     */
1735    protected void regionBefore( String headerText )
1736    {
1737        // do nothing, overridden by AggregateSink
1738    }
1739
1740    /**
1741     * Writes a 'xsl-region-after' block. By default does nothing, gets overridden by AggregateSink.
1742     *
1743     * @param footerText The text to write in the footer, if null, nothing is written.
1744     */
1745    protected void regionAfter( String footerText )
1746    {
1747        // do nothing, overridden by AggregateSink
1748    }
1749
1750    /**
1751     * Writes a chapter heading. By default does nothing, gets overridden by AggregateSink.
1752     *
1753     * @param headerText The text to write in the header, if null, the current document title is written.
1754     * @param chapterNumber True if the chapter number should be written in front of the text.
1755     */
1756    protected void chapterHeading( String headerText, boolean chapterNumber )
1757    {
1758        // do nothing, overridden by AggregateSink
1759    }
1760
1761    /**
1762     * Writes a fo:bookmark-tree. By default does nothing, gets overridden by AggregateSink.
1763     */
1764    protected void pdfBookmarks()
1765    {
1766        // do nothing, overridden by AggregateSink
1767    }
1768
1769    /**
1770     * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
1771     *
1772     * @param key not null
1773     * @param msg not null
1774     * @see #close()
1775     * @since 1.1.1
1776     */
1777    protected void logMessage( String key, String msg )
1778    {
1779        msg = "[FO Sink] " + msg;
1780        if ( getLog().isDebugEnabled() )
1781        {
1782            getLog().debug( msg );
1783
1784            return;
1785        }
1786
1787        if ( warnMessages == null )
1788        {
1789            warnMessages = new HashMap<String, Set<String>>();
1790        }
1791
1792        Set<String> set = warnMessages.get( key );
1793        if ( set == null )
1794        {
1795            set = new TreeSet<String>();
1796        }
1797        set.add( msg );
1798        warnMessages.put( key, set );
1799    }
1800
1801    /** {@inheritDoc} */
1802    protected void init()
1803    {
1804        super.init();
1805
1806        this.listStack.clear();
1807        this.tableGridStack.clear();
1808        this.cellJustifStack.clear();
1809        this.isCellJustifStack.clear();
1810        this.cellCountStack.clear();
1811        this.tableContentWriterStack.clear();
1812        this.tableCaptionWriterStack.clear();
1813        this.tableCaptionXMLWriterStack.clear();
1814        this.tableCaptionStack.clear();
1815
1816        this.section = 0;
1817        this.subsection = 0;
1818        this.subsubsection = 0;
1819        this.verbatim = false;
1820        this.inFigure = false;
1821        this.warnMessages = null;
1822    }
1823}