All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.doxia.module.fo.FoSink Maven / Gradle / Ivy

There is a newer version: 1.12.0
Show newest version
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.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
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:
 *
 * 
 * FoSink sink = new FoSink( writer );
 * sink.beginDocument();
 * ...
 * sink.endDocument();
 * 
* * @author ltheussl * @version $Id: FoSink.java 1726411 2016-01-23 16:34:09Z hboutemy $ * @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 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 tableGridStack; /** Stack of alignment int[] of table cells. */ private final LinkedList cellJustifStack; /** Stack of justification of table cells. */ private final LinkedList isCellJustifStack; /** Stack of current table cell. */ private final LinkedList cellCountStack; /** The stack of StringWriter to write the table result temporary, so we could play with the output and fix fo. */ private final LinkedList tableContentWriterStack; private final LinkedList tableCaptionWriterStack; private final LinkedList tableCaptionXMLWriterStack; /** The stack of table caption */ private final LinkedList tableCaptionStack; /** 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> warnMessages; /** * Constructor, initialize the Writer. * * @param writer not null writer to write the result. Should be an UTF-8 Writer. * You could use newXmlWriter 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 null. */ 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 null. * @param languageId language identifier for the root element as defined by * IETF BCP 47, 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(); this.tableGridStack = new LinkedList(); this.cellJustifStack = new LinkedList(); this.isCellJustifStack = new LinkedList(); this.cellCountStack = new LinkedList(); this.tableContentWriterStack = new LinkedList(); this.tableCaptionWriterStack = new LinkedList(); this.tableCaptionXMLWriterStack = new LinkedList(); this.tableCaptionStack = new LinkedList(); 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( "" ); } /** * 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" ); // 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( "" ); sb.append( EOL ); } int index = content.indexOf( ">" ) + 1; writeln( content.substring( 0, index ) ); write( sb.toString() ); write( content.substring( index ) ); writeEndTag( TABLE_TAG ); writeEOL(); // 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 ) ); // 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(); } // 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 italic() { writeStartTag( INLINE_TAG, "italic" ); } /** {@inheritDoc} */ public void italic_() { writeEndTag( INLINE_TAG ); } /** {@inheritDoc} */ public void bold() { writeStartTag( INLINE_TAG, "bold" ); } /** {@inheritDoc} */ public void bold_() { writeEndTag( INLINE_TAG ); } /** {@inheritDoc} */ public void monospaced() { writeStartTag( INLINE_TAG, "monospace" ); } /** {@inheritDoc} */ public void monospaced_() { writeEndTag( INLINE_TAG ); } /** {@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> 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( "" ); 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( "" + EOL ); } break; default: if ( needsSymbolFont( c ) ) { // TODO: make font configurable? buffer.append( "" ).append( c ).append( "" ); } 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( "" ); regionBefore( headerText ); regionAfter( footerText ); writeln( "" ); 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 msg as is, otherwise add unique msg in warnMessages. * * @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>(); } Set set = warnMessages.get( key ); if ( set == null ) { set = new TreeSet(); } 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy