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