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