| package org.apache.maven.doxia.module.fo; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.TreeSet; |
| |
| import javax.swing.text.MutableAttributeSet; |
| import javax.swing.text.html.HTML.Attribute; |
| import javax.swing.text.html.HTML.Tag; |
| |
| import org.apache.maven.doxia.sink.SinkEventAttributes; |
| import org.apache.maven.doxia.sink.impl.AbstractXmlSink; |
| import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet; |
| import org.apache.maven.doxia.sink.impl.SinkUtils; |
| import org.apache.maven.doxia.util.DoxiaUtils; |
| import org.apache.maven.doxia.util.HtmlTools; |
| |
| import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; |
| |
| /** |
| * A Doxia Sink that produces a FO model. The usage is similar to the following: |
| * |
| * <pre> |
| * FoSink sink = new FoSink( writer ); |
| * sink.beginDocument(); |
| * ... |
| * sink.endDocument(); |
| * </pre> |
| * |
| * @author ltheussl |
| * @version $Id$ |
| * @since 1.1 |
| */ |
| public class FoSink |
| extends AbstractXmlSink |
| implements FoMarkup |
| { |
| /** For writing the result. */ |
| private final PrintWriter out; |
| |
| /** Used to get the current position in numbered lists. */ |
| private final Stack<NumberedListItem> listStack; |
| |
| /** Used to get attributes for a given FO element. */ |
| private final FoConfiguration config; |
| |
| /** Counts the current section level. */ |
| private int section = 0; |
| |
| /** Counts the current subsection level. */ |
| private int subsection = 0; |
| |
| /** Counts the current subsubsection level. */ |
| private int subsubsection = 0; |
| |
| /** Verbatim flag. */ |
| private boolean verbatim; |
| |
| /** figure flag. */ |
| private boolean inFigure; |
| |
| private final String encoding; |
| |
| private final String languageId; |
| |
| /** Stack of drawing borders on table cells. */ |
| private final LinkedList<Boolean> tableGridStack; |
| |
| /** Stack of alignment int[] of table cells. */ |
| private final LinkedList<int[]> cellJustifStack; |
| |
| /** Stack of justification of table cells. */ |
| private final LinkedList<Boolean> isCellJustifStack; |
| |
| /** Stack of current table cell. */ |
| private final LinkedList<Integer> cellCountStack; |
| |
| /** The stack of StringWriter to write the table result temporary, so we could play with the output and fix fo. */ |
| private final LinkedList<StringWriter> tableContentWriterStack; |
| |
| private final LinkedList<StringWriter> tableCaptionWriterStack; |
| |
| private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack; |
| |
| /** The stack of table caption */ |
| private final LinkedList<String> tableCaptionStack; |
| |
| /** Keep track of the closing tags for inline events. */ |
| protected Stack<List<Tag>> inlineStack = new Stack<List<Tag>>(); |
| |
| /** Map of warn messages with a String as key to describe the error type and a Set as value. |
| * Using to reduce warn messages. */ |
| protected Map<String, Set<String>> warnMessages; |
| |
| /** |
| * Constructor, initialize the Writer. |
| * |
| * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer. |
| * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}. |
| */ |
| protected FoSink( Writer writer ) |
| { |
| this( writer, "UTF-8" ); |
| } |
| |
| /** |
| * Constructor, initialize the Writer and tells which encoding is used. |
| * |
| * @param writer not null writer to write the result. |
| * @param encoding the encoding used, that should be written to the generated HTML content |
| * if not <code>null</code>. |
| */ |
| protected FoSink( Writer writer, String encoding ) |
| { |
| this( writer, encoding, null ); |
| } |
| |
| /** |
| * Constructor, initialize the Writer and tells which encoding and languageId are used. |
| * |
| * @param writer not null writer to write the result. |
| * @param encoding the encoding used, that should be written to the generated HTML content |
| * if not <code>null</code>. |
| * @param languageId language identifier for the root element as defined by |
| * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages; |
| * in addition, the empty string may be specified. |
| */ |
| protected FoSink( Writer writer, String encoding, String languageId ) |
| { |
| if ( writer == null ) |
| { |
| throw new NullPointerException( "Null writer in FO Sink!" ); |
| } |
| |
| this.out = new PrintWriter( writer ); |
| this.encoding = encoding; |
| this.languageId = languageId; |
| this.config = new FoConfiguration(); |
| |
| this.listStack = new Stack<NumberedListItem>(); |
| this.tableGridStack = new LinkedList<Boolean>(); |
| this.cellJustifStack = new LinkedList<int[]>(); |
| this.isCellJustifStack = new LinkedList<Boolean>(); |
| this.cellCountStack = new LinkedList<Integer>(); |
| this.tableContentWriterStack = new LinkedList<StringWriter>(); |
| this.tableCaptionWriterStack = new LinkedList<StringWriter>(); |
| this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>(); |
| this.tableCaptionStack = new LinkedList<String>(); |
| |
| setNameSpace( "fo" ); |
| } |
| |
| // TODO add FOP compliance mode? |
| |
| /** |
| * Load configuration parameters from a File. |
| * |
| * @param configFile the configuration file. |
| * |
| * @throws java.io.IOException if the File cannot be read |
| * or some error occurs when initializing the configuration parameters. |
| * |
| * @since 1.1.1 |
| */ |
| public void load( File configFile ) |
| throws IOException |
| { |
| config.load( configFile ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void head( SinkEventAttributes attributes ) |
| { |
| init(); |
| |
| startPageSequence( "0", null, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void head() |
| { |
| head( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void head_() |
| { |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void title( SinkEventAttributes attributes ) |
| { |
| writeStartTag( BLOCK_TAG, "doc.header.title" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void title() |
| { |
| title( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void title_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void author( SinkEventAttributes attributes ) |
| { |
| writeStartTag( BLOCK_TAG, "doc.header.author" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void author() |
| { |
| author( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void author_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void date( SinkEventAttributes attributes ) |
| { |
| writeStartTag( BLOCK_TAG, "doc.header.date" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void date() |
| { |
| date( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void date_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void body( SinkEventAttributes attributes ) |
| { |
| // noop |
| } |
| |
| /** {@inheritDoc} */ |
| public void body() |
| { |
| body( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void body_() |
| { |
| writeEOL(); |
| writeEndTag( FLOW_TAG ); |
| writeEOL(); |
| writeEndTag( PAGE_SEQUENCE_TAG ); |
| writeEOL(); |
| endDocument(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // |
| // ----------------------------------------------------------------------- |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle() |
| { |
| // nop |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle_() |
| { |
| // nop |
| } |
| |
| /** {@inheritDoc} */ |
| public void section( int level, SinkEventAttributes attributes ) |
| { |
| if ( level == SECTION_LEVEL_1 ) |
| { |
| section++; |
| subsection = 0; |
| subsubsection = 0; |
| } |
| else if ( level == SECTION_LEVEL_2 ) |
| { |
| subsection++; |
| subsubsection = 0; |
| } |
| else if ( level == SECTION_LEVEL_3 ) |
| { |
| subsubsection++; |
| } |
| |
| onSection(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section_( int level ) |
| { |
| onSection_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle( int level, SinkEventAttributes attributes ) |
| { |
| onSectionTitle( level ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle_( int level ) |
| { |
| onSectionTitle_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section1() |
| { |
| section( SECTION_LEVEL_1, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle1() |
| { |
| sectionTitle( SECTION_LEVEL_1, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle1_() |
| { |
| sectionTitle_( SECTION_LEVEL_1 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section1_() |
| { |
| section_( SECTION_LEVEL_1 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section2() |
| { |
| section( SECTION_LEVEL_2, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle2() |
| { |
| sectionTitle( SECTION_LEVEL_2, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle2_() |
| { |
| sectionTitle_( SECTION_LEVEL_2 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section2_() |
| { |
| section_( SECTION_LEVEL_2 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section3() |
| { |
| section( SECTION_LEVEL_3, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle3() |
| { |
| sectionTitle( SECTION_LEVEL_3, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle3_() |
| { |
| sectionTitle_( SECTION_LEVEL_3 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section3_() |
| { |
| section_( SECTION_LEVEL_3 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section4() |
| { |
| section( SECTION_LEVEL_4, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle4() |
| { |
| sectionTitle( SECTION_LEVEL_4, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle4_() |
| { |
| sectionTitle_( SECTION_LEVEL_4 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section4_() |
| { |
| section_( SECTION_LEVEL_4 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section5() |
| { |
| section( SECTION_LEVEL_5, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle5() |
| { |
| sectionTitle( SECTION_LEVEL_5, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void sectionTitle5_() |
| { |
| sectionTitle_( SECTION_LEVEL_5 ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void section5_() |
| { |
| section_( SECTION_LEVEL_5 ); |
| } |
| |
| /** Starts a section/subsection. */ |
| private void onSection() |
| { |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, "body.text" ); |
| } |
| |
| /** |
| * Starts a section title. |
| * |
| * @param depth The section level. |
| */ |
| private void onSectionTitle( int depth ) |
| { |
| StringBuilder title = new StringBuilder( 16 ); |
| |
| title.append( getChapterString() ); |
| |
| writeEOL(); |
| if ( depth == SECTION_LEVEL_1 ) |
| { |
| writeStartTag( BLOCK_TAG, "body.h1" ); |
| title.append( section ).append( " " ); |
| } |
| else if ( depth == SECTION_LEVEL_2 ) |
| { |
| writeStartTag( BLOCK_TAG, "body.h2" ); |
| title.append( section ).append( "." ); |
| title.append( subsection ).append( " " ); |
| } |
| else if ( depth == SECTION_LEVEL_3 ) |
| { |
| writeStartTag( BLOCK_TAG, "body.h3" ); |
| title.append( section ).append( "." ); |
| title.append( subsection ).append( "." ); |
| title.append( subsubsection ).append( " " ); |
| } |
| else if ( depth == SECTION_LEVEL_4 ) |
| { |
| writeStartTag( BLOCK_TAG, "body.h4" ); |
| } |
| else |
| { |
| writeStartTag( BLOCK_TAG, "body.h5" ); |
| } |
| |
| write( title.toString() ); |
| } |
| |
| /** Ends a section title. */ |
| private void onSectionTitle_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** Ends a section/subsection. */ |
| private void onSection_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** |
| * Resets the section counter to 0. |
| * Only useful for overriding classes, like AggregateSink, the FoSink puts everything into one chapter. |
| */ |
| protected void resetSectionCounter() |
| { |
| this.section = 0; |
| } |
| |
| /** |
| * Returns the current chapter number as a string. |
| * By default does nothing, gets overridden by AggregateSink. |
| * |
| * @return an empty String. |
| */ |
| protected String getChapterString() |
| { |
| return ""; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // |
| // ----------------------------------------------------------------------- |
| |
| /** {@inheritDoc} */ |
| public void list( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeStartTag( LIST_BLOCK_TAG, "list" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void list() |
| { |
| list( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void list_() |
| { |
| writeEndTag( LIST_BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void listItem( SinkEventAttributes attributes ) |
| { |
| writeStartTag( LIST_ITEM_TAG, "list.item" ); |
| writeStartTag( LIST_ITEM_LABEL_TAG ); |
| writeStartTag( BLOCK_TAG ); |
| write( "•" ); // TODO customize? |
| writeEndTag( BLOCK_TAG ); |
| writeEndTag( LIST_ITEM_LABEL_TAG ); |
| writeEOL(); |
| writeStartTag( LIST_ITEM_BODY_TAG, "list.item" ); |
| writeEOL(); |
| writeStartTag( BLOCK_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void listItem() |
| { |
| listItem( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void listItem_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| writeEndTag( LIST_ITEM_BODY_TAG ); |
| writeEOL(); |
| writeEndTag( LIST_ITEM_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedList( int numbering, SinkEventAttributes attributes ) |
| { |
| this.listStack.push( new NumberedListItem( numbering ) ); |
| writeEOL(); |
| writeStartTag( LIST_BLOCK_TAG, "list" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedList( int numbering ) |
| { |
| numberedList( numbering, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedList_() |
| { |
| this.listStack.pop(); |
| writeEndTag( LIST_BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedListItem( SinkEventAttributes attributes ) |
| { |
| NumberedListItem current = this.listStack.peek(); |
| current.next(); |
| |
| writeStartTag( LIST_ITEM_TAG, "list.item" ); |
| |
| writeEOL(); |
| writeStartTag( LIST_ITEM_LABEL_TAG ); |
| writeEOL(); |
| writeStartTag( BLOCK_TAG ); |
| write( current.getListItemSymbol() ); |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| writeEndTag( LIST_ITEM_LABEL_TAG ); |
| writeEOL(); |
| |
| writeStartTag( LIST_ITEM_BODY_TAG, "list.item" ); |
| writeEOL(); |
| writeStartTag( BLOCK_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedListItem() |
| { |
| numberedListItem( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void numberedListItem_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| writeEndTag( LIST_ITEM_BODY_TAG ); |
| writeEOL(); |
| writeEndTag( LIST_ITEM_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionList( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, "dl" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionList() |
| { |
| definitionList( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionList_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionListItem( SinkEventAttributes attributes ) |
| { |
| // nop |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionListItem() |
| { |
| definitionListItem( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definitionListItem_() |
| { |
| // nop |
| } |
| |
| /** {@inheritDoc} */ |
| public void definedTerm( SinkEventAttributes attributes ) |
| { |
| writeStartTag( BLOCK_TAG, "dt" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definedTerm() |
| { |
| definedTerm( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definedTerm_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definition( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, "dd" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definition() |
| { |
| definition( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void definition_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figure( SinkEventAttributes attributes ) |
| { |
| this.inFigure = true; |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, "figure.display" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figure() |
| { |
| figure( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figure_() |
| { |
| this.inFigure = false; |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figureGraphics( String name ) |
| { |
| figureGraphics( name, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figureGraphics( String src, SinkEventAttributes attributes ) |
| { |
| MutableAttributeSet atts = config.getAttributeSet( "figure.graphics" ); |
| atts.addAttribute( Attribute.SRC.toString(), src ); |
| |
| // http://xmlgraphics.apache.org/fop/graphics.html#resolution |
| |
| final String[] valids = new String[] {"content-height", "content-width", "height", "width"}; |
| final MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, valids ); |
| |
| if ( filtered != null ) |
| { |
| atts.addAttributes( filtered ); |
| } |
| |
| writeln( "<fo:external-graphic" + SinkUtils.getAttributeString( atts ) + "/>" ); |
| } |
| |
| /** |
| * Flags if we are inside a figure. |
| * |
| * @return True if we are between {@link #figure()} and {@link #figure_()} calls. |
| */ |
| protected boolean isFigure() |
| { |
| return this.inFigure; |
| } |
| |
| /** {@inheritDoc} */ |
| public void figureCaption( SinkEventAttributes attributes ) |
| { |
| writeStartTag( BLOCK_TAG, "figure.caption" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figureCaption() |
| { |
| figureCaption( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void figureCaption_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void paragraph() |
| { |
| paragraph( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void paragraph( SinkEventAttributes attributes ) |
| { |
| MutableAttributeSet atts = config.getAttributeSet( "normal.paragraph" ); |
| |
| if ( attributes != null && attributes.isDefined( SinkEventAttributes.ALIGN ) ) |
| { |
| atts.addAttribute( "text-align", attributes.getAttribute( SinkEventAttributes.ALIGN ) ); |
| } |
| |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, atts ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void paragraph_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void verbatim( SinkEventAttributes attributes ) |
| { |
| this.verbatim = true; |
| |
| boolean boxed = false; |
| |
| if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) ) |
| { |
| boxed = |
| "boxed".equals( attributes.getAttribute( SinkEventAttributes.DECORATION ).toString() ); |
| } |
| |
| if ( boxed ) |
| { |
| writeStartTag( BLOCK_TAG, "body.source" ); |
| } |
| else |
| { |
| writeStartTag( BLOCK_TAG, "body.pre" ); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void verbatim( boolean boxed ) |
| { |
| verbatim( boxed ? SinkEventAttributeSet.BOXED : null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void verbatim_() |
| { |
| this.verbatim = false; |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void horizontalRule( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeEOL(); |
| writeStartTag( BLOCK_TAG ); |
| writeEmptyTag( LEADER_TAG, "body.rule" ); |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void horizontalRule() |
| { |
| horizontalRule( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void pageBreak() |
| { |
| writeEmptyTag( BLOCK_TAG, "break-before", "page" ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void table( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, "table.padding" ); |
| |
| // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95 |
| //writeStartTag( TABLE_AND_CAPTION_TAG ); |
| |
| this.tableContentWriterStack.addLast( new StringWriter() ); |
| writeStartTag( TABLE_TAG, "table.layout" ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void table() |
| { |
| table( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void table_() |
| { |
| String content = this.tableContentWriterStack.removeLast().toString(); |
| |
| StringBuilder sb = new StringBuilder(); |
| int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() ); |
| for ( int i = 0; i < cellCount; i++ ) |
| { |
| sb.append( "<fo:table-column column-width=\"proportional-column-width(1)\"/>" ); |
| sb.append( EOL ); |
| } |
| |
| int index = content.indexOf( ">" ) + 1; |
| writeln( content.substring( 0, index ) ); |
| write( sb.toString() ); |
| write( content.substring( index ) ); |
| |
| writeEndTag( TABLE_TAG ); |
| writeEOL(); |
| |
| // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95 |
| //writeEndTag( TABLE_AND_CAPTION_TAG ); |
| |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| |
| if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null ) |
| { |
| paragraph( SinkEventAttributeSet.CENTER ); |
| write( this.tableCaptionStack.removeLast().toString() ); |
| paragraph_(); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableRows( int[] justification, boolean grid ) |
| { |
| this.tableGridStack.addLast( Boolean.valueOf( grid ) ); |
| this.cellJustifStack.addLast( justification ); |
| this.isCellJustifStack.addLast( Boolean.valueOf( true ) ); |
| this.cellCountStack.addLast( Integer.valueOf( 0 ) ); |
| writeEOL(); |
| writeStartTag( TABLE_BODY_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableRows_() |
| { |
| this.tableGridStack.removeLast(); |
| this.cellJustifStack.removeLast(); |
| this.isCellJustifStack.removeLast(); |
| writeEndTag( TABLE_BODY_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableRow( SinkEventAttributes attributes ) |
| { |
| // TODO spacer rows |
| writeStartTag( TABLE_ROW_TAG, "table.body.row" ); |
| this.cellCountStack.removeLast(); |
| this.cellCountStack.addLast( Integer.valueOf( 0 ) ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableRow() |
| { |
| tableRow( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableRow_() |
| { |
| writeEndTag( TABLE_ROW_TAG ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCell( SinkEventAttributes attributes ) |
| { |
| tableCell( false, attributes ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCell() |
| { |
| tableCell( (SinkEventAttributes) null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCell( String width ) |
| { |
| // TODO: fop can't handle cell width |
| tableCell(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableHeaderCell( SinkEventAttributes attributes ) |
| { |
| tableCell( true, attributes ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableHeaderCell() |
| { |
| tableHeaderCell( (SinkEventAttributes) null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableHeaderCell( String width ) |
| { |
| // TODO: fop can't handle cell width |
| tableHeaderCell(); |
| } |
| |
| /** |
| * Writes a table cell. |
| * |
| * @param headerRow true if this is a header cell. |
| * @param attributes the cell attributes, could be null. |
| */ |
| private void tableCell( boolean headerRow, SinkEventAttributes attributes ) |
| { |
| MutableAttributeSet cellAtts = headerRow |
| ? config.getAttributeSet( "table.heading.cell" ) |
| : config.getAttributeSet( "table.body.cell" ); |
| |
| // the column-number is needed for the hack to center the table, see tableRows. |
| int cellCount = Integer.parseInt( this.cellCountStack.getLast().toString() ); |
| cellAtts.addAttribute( "column-number", String.valueOf( cellCount + 1 ) ); |
| |
| if ( this.tableGridStack.getLast().equals( Boolean.TRUE ) ) |
| { |
| cellAtts.addAttributes( config.getAttributeSet( "table.body.cell.grid" ) ); |
| } |
| |
| MutableAttributeSet blockAtts = headerRow |
| ? config.getAttributeSet( "table.heading.block" ) |
| : config.getAttributeSet( "table.body.block" ); |
| |
| String justif = null; |
| if ( attributes == null ) |
| { |
| attributes = new SinkEventAttributeSet( 0 ); |
| } |
| |
| if ( attributes.isDefined( Attribute.ALIGN.toString() ) ) |
| { |
| justif = attributes.getAttribute( Attribute.ALIGN.toString() ).toString(); |
| } |
| |
| int[] cellJustif = this.cellJustifStack.getLast(); |
| if ( justif == null && cellJustif != null && cellJustif.length > 0 |
| && this.isCellJustifStack.getLast().equals( Boolean.TRUE ) ) |
| { |
| switch ( cellJustif[Math.min( cellCount, cellJustif.length - 1 )] ) |
| { |
| case JUSTIFY_LEFT: |
| justif = "left"; |
| break; |
| case JUSTIFY_RIGHT: |
| justif = "right"; |
| break; |
| case JUSTIFY_CENTER: |
| default: |
| justif = "center"; |
| } |
| } |
| |
| if ( justif != null ) |
| { |
| blockAtts.addAttribute( "text-align", justif ); |
| } |
| |
| writeStartTag( TABLE_CELL_TAG, cellAtts ); |
| writeEOL(); |
| writeStartTag( BLOCK_TAG, blockAtts ); |
| writeEOL(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCell_() |
| { |
| writeEndTag( BLOCK_TAG ); |
| writeEOL(); |
| writeEndTag( TABLE_CELL_TAG ); |
| writeEOL(); |
| |
| if ( this.isCellJustifStack.getLast().equals( Boolean.TRUE ) ) |
| { |
| int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() ); |
| this.cellCountStack.addLast( Integer.valueOf( ++cellCount ) ); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableHeaderCell_() |
| { |
| tableCell_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCaption( SinkEventAttributes attributes ) |
| { |
| StringWriter sw = new StringWriter(); |
| this.tableCaptionWriterStack.addLast( sw ); |
| this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) ); |
| |
| // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95 |
| //writeStartTag( TABLE_CAPTION_TAG ); |
| |
| // TODO: how to implement this otherwise? |
| // table-footer doesn't work because it has to be declared before table-body. |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCaption() |
| { |
| tableCaption( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void tableCaption_() |
| { |
| if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null ) |
| { |
| this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() ); |
| this.tableCaptionXMLWriterStack.removeLast(); |
| } |
| // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95 |
| //writeEndTag( TABLE_CAPTION_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void anchor( String name, SinkEventAttributes attributes ) |
| { |
| if ( name == null ) |
| { |
| throw new NullPointerException( "Anchor name cannot be null!" ); |
| } |
| |
| String anchor = name; |
| |
| if ( !DoxiaUtils.isValidId( anchor ) ) |
| { |
| anchor = DoxiaUtils.encodeId( name, true ); |
| |
| String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'"; |
| logMessage( "modifiedLink", msg ); |
| } |
| |
| anchor = "#" + name; |
| |
| writeStartTag( INLINE_TAG, "id", anchor ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void anchor( String name ) |
| { |
| anchor( name, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void anchor_() |
| { |
| writeEndTag( INLINE_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void link( String name, SinkEventAttributes attributes ) |
| { |
| if ( name == null ) |
| { |
| throw new NullPointerException( "Link name cannot be null!" ); |
| } |
| |
| if ( DoxiaUtils.isExternalLink( name ) ) |
| { |
| writeStartTag( BASIC_LINK_TAG, "external-destination", HtmlTools.escapeHTML( name ) ); |
| writeStartTag( INLINE_TAG, "href.external" ); |
| } |
| else if ( DoxiaUtils.isInternalLink( name ) ) |
| { |
| String anchor = name.substring( 1 ); |
| |
| if ( !DoxiaUtils.isValidId( anchor ) ) |
| { |
| anchor = DoxiaUtils.encodeId( anchor, true ); |
| |
| String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'"; |
| logMessage( "modifiedLink", msg ); |
| } |
| |
| anchor = "#" + anchor; |
| |
| writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) ); |
| writeStartTag( INLINE_TAG, "href.internal" ); |
| } |
| else |
| { |
| // treat everything else as is |
| String anchor = name; |
| |
| writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) ); |
| writeStartTag( INLINE_TAG, "href.internal" ); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void link( String name ) |
| { |
| link( name, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void link_() |
| { |
| writeEndTag( INLINE_TAG ); |
| writeEndTag( BASIC_LINK_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void inline() |
| { |
| inline( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void inline( SinkEventAttributes attributes ) |
| { |
| List<Tag> tags = new ArrayList<Tag>(); |
| |
| if ( attributes != null ) |
| { |
| |
| if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) ) |
| { |
| writeStartTag( INLINE_TAG, "italic" ); |
| tags.add( 0, INLINE_TAG ); |
| } |
| |
| if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) ) |
| { |
| writeStartTag( INLINE_TAG, "bold" ); |
| tags.add( 0, INLINE_TAG ); |
| } |
| |
| if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) ) |
| { |
| writeStartTag( INLINE_TAG, "monospace" ); |
| tags.add( 0, INLINE_TAG ); |
| } |
| |
| } |
| |
| inlineStack.push( tags ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void inline_() |
| { |
| for ( Tag tag: inlineStack.pop() ) |
| { |
| writeEndTag( tag ); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void italic() |
| { |
| inline( SinkEventAttributeSet.Semantics.ITALIC ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void italic_() |
| { |
| inline_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void bold() |
| { |
| inline( SinkEventAttributeSet.Semantics.BOLD ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void bold_() |
| { |
| inline_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void monospaced() |
| { |
| inline( SinkEventAttributeSet.Semantics.CODE ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void monospaced_() |
| { |
| inline_(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void lineBreak( SinkEventAttributes attributes ) |
| { |
| writeEOL(); |
| writeEOL(); |
| writeSimpleTag( BLOCK_TAG ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void lineBreak() |
| { |
| lineBreak( null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void nonBreakingSpace() |
| { |
| write( " " ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void text( String text, SinkEventAttributes attributes ) |
| { |
| content( text ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void text( String text ) |
| { |
| text( text, null ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void rawText( String text ) |
| { |
| write( text ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void flush() |
| { |
| out.flush(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void close() |
| { |
| out.close(); |
| |
| if ( getLog().isWarnEnabled() && this.warnMessages != null ) |
| { |
| for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() ) |
| { |
| for ( String msg : entry.getValue() ) |
| { |
| getLog().warn( msg ); |
| } |
| } |
| |
| this.warnMessages = null; |
| } |
| |
| init(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Unkown events just log a warning message but are ignored otherwise. |
| * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes) |
| */ |
| public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes ) |
| { |
| String msg = "Unknown Sink event: '" + name + "', ignoring!"; |
| logMessage( "unknownEvent", msg ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void comment( String comment ) |
| { |
| if ( comment != null ) |
| { |
| final String originalComment = comment; |
| |
| // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments |
| while ( comment.contains( "--" ) ) |
| { |
| comment = comment.replace( "--", "- -" ); |
| } |
| |
| if ( comment.endsWith( "-" ) ) |
| { |
| comment += " "; |
| } |
| |
| if ( !originalComment.equals( comment ) ) |
| { |
| String msg = "Modified invalid comment: '" + originalComment + "' to '" + comment + "'"; |
| logMessage( "modifyComment", msg ); |
| } |
| |
| final StringBuilder buffer = new StringBuilder( comment.length() + 7 ); |
| |
| buffer.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS ); |
| buffer.append( comment ); |
| buffer.append( MINUS ).append( MINUS ).append( GREATER_THAN ); |
| |
| write( buffer.toString() ); |
| } |
| } |
| |
| /** |
| * Writes the beginning of a FO document. |
| */ |
| public void beginDocument() |
| { |
| write( "<?xml version=\"1.0\"" ); |
| if ( encoding != null ) |
| { |
| write( " encoding=\"" + encoding + "\"" ); |
| } |
| write( "?>" ); |
| writeEOL(); |
| |
| MutableAttributeSet atts = new SinkEventAttributeSet(); |
| atts.addAttribute( "xmlns:" + getNameSpace(), FO_NAMESPACE ); |
| |
| if ( languageId != null ) |
| { |
| atts.addAttribute( "language", languageId ); |
| } |
| |
| writeStartTag( ROOT_TAG, atts ); |
| |
| writeStartTag( LAYOUT_MASTER_SET_TAG ); |
| |
| writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.cover-page" ); |
| writeEmptyTag( REGION_BODY_TAG, "layout.master.set.cover-page.region-body" ); |
| writeEndTag( SIMPLE_PAGE_MASTER_TAG ); |
| writeEOL(); |
| |
| writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.toc" ); |
| writeEmptyTag( REGION_BODY_TAG, "layout.master.set.toc.region-body" ); |
| writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.toc.region-before" ); |
| writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.toc.region-after" ); |
| writeEndTag( SIMPLE_PAGE_MASTER_TAG ); |
| writeEOL(); |
| |
| writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.body" ); |
| writeEmptyTag( REGION_BODY_TAG, "layout.master.set.body.region-body" ); |
| writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.body.region-before" ); |
| writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.body.region-after" ); |
| writeEndTag( SIMPLE_PAGE_MASTER_TAG ); |
| writeEOL(); |
| |
| writeEndTag( LAYOUT_MASTER_SET_TAG ); |
| writeEOL(); |
| |
| pdfBookmarks(); |
| } |
| |
| /** |
| * Writes the end of a FO document, flushes and closes the stream. |
| */ |
| public void endDocument() |
| { |
| writeEndTag( ROOT_TAG ); |
| writeEOL(); |
| |
| flush(); |
| close(); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Returns the configuration object of this sink. |
| * |
| * @return The configuration object of this sink. |
| */ |
| protected FoConfiguration getFoConfiguration() |
| { |
| return config; |
| } |
| |
| /** |
| * Writes a start tag, prepending EOL. |
| * |
| * @param tag The tag. |
| * @param attributeId An id identifying the attribute set. |
| */ |
| protected void writeStartTag( Tag tag, String attributeId ) |
| { |
| writeEOL(); |
| writeStartTag( tag, config.getAttributeSet( attributeId ) ); |
| } |
| |
| /** |
| * Writes a start tag, prepending EOL. |
| * |
| * @param tag The tag. |
| * @param id An id to add. |
| * @param name The name (value) of the id. |
| */ |
| protected void writeStartTag( Tag tag, String id, String name ) |
| { |
| writeEOL(); |
| MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} ); |
| |
| writeStartTag( tag, att ); |
| } |
| |
| /** |
| * Writes a start tag, prepending EOL. |
| * |
| * @param tag The tag. |
| * @param id An id to add. |
| * @param name The name (value) of the id. |
| * @param attributeId An id identifying the attribute set. |
| */ |
| protected void writeStartTag( Tag tag, String id, String name, String attributeId ) |
| { |
| MutableAttributeSet att = config.getAttributeSet( attributeId ); |
| |
| // make sure we don't add it twice |
| if ( att.isDefined( id ) ) |
| { |
| att.removeAttribute( id ); |
| } |
| |
| att.addAttribute( id, name ); |
| |
| writeEOL(); |
| writeStartTag( tag, att ); |
| } |
| |
| /** |
| * Writes an empty tag, prepending EOL. |
| * |
| * @param tag The tag. |
| * @param id An id to add. |
| * @param name The name (value) of the id. |
| */ |
| protected void writeEmptyTag( Tag tag, String id, String name ) |
| { |
| MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} ); |
| |
| writeEOL(); |
| writeSimpleTag( tag, att ); |
| } |
| |
| /** |
| * Writes a simple tag, appending EOL. |
| * |
| * @param tag The tag name. |
| * @param attributeId An id identifying the attribute set. |
| */ |
| protected void writeEmptyTag( Tag tag, String attributeId ) |
| { |
| writeEOL(); |
| writeSimpleTag( tag, config.getAttributeSet( attributeId ) ); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Writes a text, swallowing any exceptions. |
| */ |
| protected void write( String text ) |
| { |
| if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null ) |
| { |
| this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) ); |
| } |
| else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null ) |
| { |
| this.tableContentWriterStack.getLast().write( unifyEOLs( text ) ); |
| } |
| else |
| { |
| out.write( unifyEOLs( text ) ); |
| } |
| } |
| |
| /** |
| * Writes a text, appending EOL. |
| * |
| * @param text The text to write. |
| */ |
| protected void writeln( String text ) |
| { |
| write( text ); |
| writeEOL(); |
| } |
| |
| /** |
| * Writes content, escaping special characters. |
| * |
| * @param text The text to write. |
| */ |
| protected void content( String text ) |
| { |
| write( escaped( text, verbatim ) ); |
| } |
| |
| /** |
| * Escapes special characters so that the text can be included in a fo file. |
| * |
| * @param text The text to process. |
| * @param verb In verbatim mode, white space and newlines are escaped. |
| * @return The text with special characters escaped. |
| */ |
| public static String escaped( String text, boolean verb ) |
| { |
| int length = text.length(); |
| StringBuilder buffer = new StringBuilder( length ); |
| |
| for ( int i = 0; i < length; ++i ) |
| { |
| char c = text.charAt( i ); |
| switch ( c ) |
| { |
| case ' ': |
| if ( verb ) |
| { |
| buffer.append( " " ); |
| } |
| else |
| { |
| buffer.append( c ); |
| } |
| break; |
| case '<': |
| buffer.append( "<" ); |
| break; |
| case '>': |
| buffer.append( ">" ); |
| break; |
| case '&': |
| buffer.append( "&" ); |
| break; |
| case '\n': |
| buffer.append( EOL ); |
| if ( verb ) |
| { |
| buffer.append( "<fo:block/>" + EOL ); |
| } |
| break; |
| default: |
| if ( needsSymbolFont( c ) ) |
| { |
| // TODO: make font configurable? |
| buffer.append( "<fo:inline font-family=\"Symbol\">" ).append( c ).append( "</fo:inline>" ); |
| } |
| else |
| { |
| buffer.append( c ); |
| } |
| } |
| } |
| |
| return buffer.toString(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag ) |
| { |
| if ( this.tableCaptionXMLWriterStack.isEmpty() ) |
| { |
| super.writeStartTag ( t, att, isSimpleTag ); |
| } |
| else |
| { |
| String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString(); |
| this.tableCaptionXMLWriterStack.getLast().startElement( tag ); |
| |
| if ( att != null ) |
| { |
| Enumeration<?> names = att.getAttributeNames(); |
| while ( names.hasMoreElements() ) |
| { |
| Object key = names.nextElement(); |
| Object value = att.getAttribute( key ); |
| |
| this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() ); |
| } |
| } |
| |
| if ( isSimpleTag ) |
| { |
| this.tableCaptionXMLWriterStack.getLast().endElement(); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void writeEndTag( Tag t ) |
| { |
| if ( this.tableCaptionXMLWriterStack.isEmpty() ) |
| { |
| super.writeEndTag( t ); |
| } |
| else |
| { |
| this.tableCaptionXMLWriterStack.getLast().endElement(); |
| } |
| } |
| |
| private static final char UPPER_ALPHA = 0x391; |
| private static final char PIV = 0x3d6; |
| private static final char OLINE = 0x203e; |
| private static final char DIAMS = 0x2666; |
| private static final char EURO = 0x20ac; |
| private static final char TRADE = 0x2122; |
| private static final char PRIME = 0x2032; |
| private static final char PPRIME = 0x2033; |
| |
| private static boolean needsSymbolFont( char c ) |
| { |
| // greek characters and mathematical symbols, except the euro and trade symbols |
| // symbols I couldn't get to display in any font: |
| // zwnj (0x200C), zwj (0x200D), lrm (0x200E), rlm (0x200F), oline (0x203E), |
| // lceil (0x2038), rceil (0x2039), lfloor (0x203A), rfloor (0x203B) |
| return ( c >= UPPER_ALPHA && c <= PIV ) |
| || ( c == PRIME || c == PPRIME ) |
| || ( c >= OLINE && c <= DIAMS && c != EURO && c != TRADE ); |
| } |
| |
| /** |
| * Starts a page sequence. |
| * |
| * @param initPageNumber The initial page number. Should be either "0" (for the first page) or "auto". |
| * @param headerText The text to write in the header, if null, nothing is written. |
| * @param footerText The text to write in the footer, if null, nothing is written. |
| */ |
| protected void startPageSequence( String initPageNumber, String headerText, String footerText ) |
| { |
| writeln( "<fo:page-sequence initial-page-number=\"" + initPageNumber + "\" master-reference=\"body\">" ); |
| regionBefore( headerText ); |
| regionAfter( footerText ); |
| writeln( "<fo:flow flow-name=\"xsl-region-body\">" ); |
| chapterHeading( null, true ); |
| } |
| |
| /** |
| * Writes a 'xsl-region-before' block. |
| * |
| * @param headerText The text to write in the header, if null, nothing is written. |
| */ |
| protected void regionBefore( String headerText ) |
| { |
| // do nothing, overridden by AggregateSink |
| } |
| |
| /** |
| * Writes a 'xsl-region-after' block. By default does nothing, gets overridden by AggregateSink. |
| * |
| * @param footerText The text to write in the footer, if null, nothing is written. |
| */ |
| protected void regionAfter( String footerText ) |
| { |
| // do nothing, overridden by AggregateSink |
| } |
| |
| /** |
| * Writes a chapter heading. By default does nothing, gets overridden by AggregateSink. |
| * |
| * @param headerText The text to write in the header, if null, the current document title is written. |
| * @param chapterNumber True if the chapter number should be written in front of the text. |
| */ |
| protected void chapterHeading( String headerText, boolean chapterNumber ) |
| { |
| // do nothing, overridden by AggregateSink |
| } |
| |
| /** |
| * Writes a fo:bookmark-tree. By default does nothing, gets overridden by AggregateSink. |
| */ |
| protected void pdfBookmarks() |
| { |
| // do nothing, overridden by AggregateSink |
| } |
| |
| /** |
| * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>. |
| * |
| * @param key not null |
| * @param msg not null |
| * @see #close() |
| * @since 1.1.1 |
| */ |
| protected void logMessage( String key, String msg ) |
| { |
| msg = "[FO Sink] " + msg; |
| if ( getLog().isDebugEnabled() ) |
| { |
| getLog().debug( msg ); |
| |
| return; |
| } |
| |
| if ( warnMessages == null ) |
| { |
| warnMessages = new HashMap<String, Set<String>>(); |
| } |
| |
| Set<String> set = warnMessages.get( key ); |
| if ( set == null ) |
| { |
| set = new TreeSet<String>(); |
| } |
| set.add( msg ); |
| warnMessages.put( key, set ); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void init() |
| { |
| super.init(); |
| |
| this.listStack.clear(); |
| this.tableGridStack.clear(); |
| this.cellJustifStack.clear(); |
| this.isCellJustifStack.clear(); |
| this.cellCountStack.clear(); |
| this.tableContentWriterStack.clear(); |
| this.tableCaptionWriterStack.clear(); |
| this.tableCaptionXMLWriterStack.clear(); |
| this.tableCaptionStack.clear(); |
| |
| this.section = 0; |
| this.subsection = 0; |
| this.subsubsection = 0; |
| this.verbatim = false; |
| this.inFigure = false; |
| this.warnMessages = null; |
| } |
| } |