blob: 4ad403a47ba8a7378fe373bb70a20a24bab1d9f2 [file] [log] [blame]
package org.apache.maven.doxia.sink.impl;
/*
* 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.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.EmptyStackException;
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.markup.HtmlMarkup;
import org.apache.maven.doxia.markup.Markup;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.util.DoxiaUtils;
import org.apache.maven.doxia.util.HtmlTools;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
/**
* Abstract base xhtml5 sink implementation.
*/
public class Xhtml5BaseSink
extends AbstractXmlSink
implements HtmlMarkup
{
// ----------------------------------------------------------------------
// Instance fields
// ----------------------------------------------------------------------
/** The PrintWriter to write the result. */
private final PrintWriter writer;
/** Used to collect text events mainly for the head events. */
private StringBuffer textBuffer = new StringBuffer();
/** An indication on if we're inside a head. */
private boolean headFlag;
/** Keep track of the main and div tags for content events. */
protected Stack<Tag> contentStack = new Stack<Tag>();
/** Keep track of the closing tags for inline events. */
protected Stack<List<Tag>> inlineStack = new Stack<List<Tag>>();
/** An indication on if we're inside a paragraph flag. */
private boolean paragraphFlag;
/** An indication on if we're in verbatim mode. */
private boolean verbatimFlag;
/** 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;
/** Used to style successive table rows differently. */
private boolean evenTableRow = true;
/** The stack of StringWriter to write the table result temporary, so we could play with the output DOXIA-177. */
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;
/** used to store attributes passed to table(). */
protected MutableAttributeSet tableAttributes;
/** Flag to know if {@link #tableRows(int[], boolean)} is called or not. It is mainly to be backward compatible
* with some plugins (like checkstyle) which uses:
* <pre>
* sink.table();
* sink.tableRow();
* </pre>
* instead of
* <pre>
* sink.table();
* sink.tableRows( justify, true );
* sink.tableRow();
* </pre>
* */
protected boolean tableRows = false;
/** Map of warn messages with a String as key to describe the error type and a Set as value.
* Using to reduce warn messages. */
private Map<String, Set<String>> warnMessages;
// ----------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------
/**
* Constructor, initialize the PrintWriter.
*
* @param out The writer to write the result.
*/
public Xhtml5BaseSink( Writer out )
{
this.writer = new PrintWriter( out );
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>();
init();
}
// ----------------------------------------------------------------------
// Accessor methods
// ----------------------------------------------------------------------
/**
* To use mainly when playing with the head events.
*
* @return the current buffer of text events.
*/
protected StringBuffer getTextBuffer()
{
return this.textBuffer;
}
/**
* <p>Setter for the field <code>headFlag</code>.</p>
*
* @param headFlag an header flag.
*/
protected void setHeadFlag( boolean headFlag )
{
this.headFlag = headFlag;
}
/**
* <p>isHeadFlag.</p>
*
* @return the current headFlag.
*/
protected boolean isHeadFlag()
{
return this.headFlag ;
}
/**
* <p>Setter for the field <code>verbatimFlag</code>.</p>
*
* @param verb a verbatim flag.
*/
protected void setVerbatimFlag( boolean verb )
{
this.verbatimFlag = verb;
}
/**
* <p>isVerbatimFlag.</p>
*
* @return the current verbatim flag.
*/
protected boolean isVerbatimFlag()
{
return this.verbatimFlag ;
}
/**
* <p>Setter for the field <code>cellJustif</code>.</p>
*
* @param justif the new cell justification array.
*/
protected void setCellJustif( int[] justif )
{
this.cellJustifStack.addLast( justif );
this.isCellJustifStack.addLast( Boolean.TRUE );
}
/**
* <p>Getter for the field <code>cellJustif</code>.</p>
*
* @return the current cell justification array.
*/
protected int[] getCellJustif()
{
return this.cellJustifStack.getLast();
}
/**
* <p>Setter for the field <code>cellCount</code>.</p>
*
* @param count the new cell count.
*/
protected void setCellCount( int count )
{
this.cellCountStack.addLast( count );
}
/**
* <p>Getter for the field <code>cellCount</code>.</p>
*
* @return the current cell count.
*/
protected int getCellCount()
{
return Integer.parseInt( this.cellCountStack.getLast().toString() );
}
/**
* Reset all variables.
*
* @deprecated since 1.1.2, use {@link #init()} instead of.
*/
protected void resetState()
{
init();
}
/** {@inheritDoc} */
@Override
protected void init()
{
super.init();
resetTextBuffer();
this.cellJustifStack.clear();
this.isCellJustifStack.clear();
this.cellCountStack.clear();
this.tableContentWriterStack.clear();
this.tableCaptionWriterStack.clear();
this.tableCaptionXMLWriterStack.clear();
this.tableCaptionStack.clear();
this.inlineStack.clear();
this.headFlag = false;
this.paragraphFlag = false;
this.verbatimFlag = false;
this.evenTableRow = true;
this.tableAttributes = null;
this.tableRows = false;
this.warnMessages = null;
}
/**
* Reset the text buffer.
*/
protected void resetTextBuffer()
{
this.textBuffer = new StringBuffer();
}
// ----------------------------------------------------------------------
// Sections
// ----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public void article()
{
article( null );
}
/**
* {@inheritDoc}
*/
@Override
public void article( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.ARTICLE, atts );
}
/**
* {@inheritDoc}
*/
@Override
public void article_()
{
writeEndTag( HtmlMarkup.ARTICLE );
}
/**
* {@inheritDoc}
*/
@Override
public void navigation()
{
navigation( null );
}
/**
* {@inheritDoc}
*/
@Override
public void navigation( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.NAV, atts );
}
/**
* {@inheritDoc}
*/
@Override
public void navigation_()
{
writeEndTag( HtmlMarkup.NAV );
}
/**
* {@inheritDoc}
*/
@Override
public void sidebar()
{
sidebar( null );
}
/**
* {@inheritDoc}
*/
@Override
public void sidebar( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.ASIDE, atts );
}
/**
* {@inheritDoc}
*/
@Override
public void sidebar_()
{
writeEndTag( HtmlMarkup.ASIDE );
}
/** {@inheritDoc} */
@Override
public void section( int level, SinkEventAttributes attributes )
{
onSection( level, attributes );
}
/** {@inheritDoc} */
@Override
public void sectionTitle( int level, SinkEventAttributes attributes )
{
onSectionTitle( level, attributes );
}
/** {@inheritDoc} */
@Override
public void sectionTitle_( int level )
{
onSectionTitle_( level );
}
/** {@inheritDoc} */
@Override
public void section_( int level )
{
onSection_( level );
}
/** {@inheritDoc} */
@Override
public void section1()
{
onSection( SECTION_LEVEL_1, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle1()
{
onSectionTitle( SECTION_LEVEL_1, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle1_()
{
onSectionTitle_( SECTION_LEVEL_1 );
}
/** {@inheritDoc} */
@Override
public void section1_()
{
onSection_( SECTION_LEVEL_1 );
}
/** {@inheritDoc} */
@Override
public void section2()
{
onSection( SECTION_LEVEL_2, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle2()
{
onSectionTitle( SECTION_LEVEL_2, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle2_()
{
onSectionTitle_( SECTION_LEVEL_2 );
}
/** {@inheritDoc} */
@Override
public void section2_()
{
onSection_( SECTION_LEVEL_2 );
}
/** {@inheritDoc} */
@Override
public void section3()
{
onSection( SECTION_LEVEL_3, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle3()
{
onSectionTitle( SECTION_LEVEL_3, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle3_()
{
onSectionTitle_( SECTION_LEVEL_3 );
}
/** {@inheritDoc} */
@Override
public void section3_()
{
onSection_( SECTION_LEVEL_3 );
}
/** {@inheritDoc} */
@Override
public void section4()
{
onSection( SECTION_LEVEL_4, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle4()
{
onSectionTitle( SECTION_LEVEL_4, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle4_()
{
onSectionTitle_( SECTION_LEVEL_4 );
}
/** {@inheritDoc} */
@Override
public void section4_()
{
onSection_( SECTION_LEVEL_4 );
}
/** {@inheritDoc} */
@Override
public void section5()
{
onSection( SECTION_LEVEL_5, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle5()
{
onSectionTitle( SECTION_LEVEL_5, null );
}
/** {@inheritDoc} */
@Override
public void sectionTitle5_()
{
onSectionTitle_( SECTION_LEVEL_5 );
}
/** {@inheritDoc} */
@Override
public void section5_()
{
onSection_( SECTION_LEVEL_5 );
}
/**
* Starts a section. The default class style is <code>section</code>.
*
* @param depth The level of the section.
* @param attributes some attributes. May be null.
* @see javax.swing.text.html.HTML.Tag#SECTION
*/
protected void onSection( int depth, SinkEventAttributes attributes )
{
if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
{
MutableAttributeSet att = new SinkEventAttributeSet();
att.addAttributes( SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES ) );
writeStartTag( HtmlMarkup.SECTION, att );
}
}
/**
* Ends a section.
*
* @param depth The level of the section.
* @see javax.swing.text.html.HTML.Tag#DIV
*/
protected void onSection_( int depth )
{
if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
{
writeEndTag( HtmlMarkup.SECTION );
}
}
/**
* Starts a section title.
*
* @param depth The level of the section title.
* @param attributes some attributes. May be null.
* @see javax.swing.text.html.HTML.Tag#H2
* @see javax.swing.text.html.HTML.Tag#H3
* @see javax.swing.text.html.HTML.Tag#H4
* @see javax.swing.text.html.HTML.Tag#H5
* @see javax.swing.text.html.HTML.Tag#H6
*/
protected void onSectionTitle( int depth, SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
if ( depth == SECTION_LEVEL_1 )
{
writeStartTag( HtmlMarkup.H2, atts );
}
else if ( depth == SECTION_LEVEL_2 )
{
writeStartTag( HtmlMarkup.H3, atts );
}
else if ( depth == SECTION_LEVEL_3 )
{
writeStartTag( HtmlMarkup.H4, atts );
}
else if ( depth == SECTION_LEVEL_4 )
{
writeStartTag( HtmlMarkup.H5, atts );
}
else if ( depth == SECTION_LEVEL_5 )
{
writeStartTag( HtmlMarkup.H6, atts );
}
}
/**
* Ends a section title.
*
* @param depth The level of the section title.
* @see javax.swing.text.html.HTML.Tag#H2
* @see javax.swing.text.html.HTML.Tag#H3
* @see javax.swing.text.html.HTML.Tag#H4
* @see javax.swing.text.html.HTML.Tag#H5
* @see javax.swing.text.html.HTML.Tag#H6
*/
protected void onSectionTitle_( int depth )
{
if ( depth == SECTION_LEVEL_1 )
{
writeEndTag( HtmlMarkup.H2 );
}
else if ( depth == SECTION_LEVEL_2 )
{
writeEndTag( HtmlMarkup.H3 );
}
else if ( depth == SECTION_LEVEL_3 )
{
writeEndTag( HtmlMarkup.H4 );
}
else if ( depth == SECTION_LEVEL_4 )
{
writeEndTag( HtmlMarkup.H5 );
}
else if ( depth == SECTION_LEVEL_5 )
{
writeEndTag( HtmlMarkup.H6 );
}
}
/**
* {@inheritDoc}
*/
@Override
public void header()
{
header( null );
}
/**
* {@inheritDoc}
*/
@Override
public void header( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.HEADER, atts );
}
/**
* {@inheritDoc}
*/
@Override
public void header_()
{
writeEndTag( HtmlMarkup.HEADER );
}
/**
* {@inheritDoc}
*/
@Override
public void content()
{
content( (SinkEventAttributes) null );
}
/**
* {@inheritDoc}
*/
@Override
public void content( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
if ( contentStack.empty() )
{
writeStartTag( contentStack.push( HtmlMarkup.MAIN ), atts );
}
else
{
if ( atts == null )
{
atts = new SinkEventAttributeSet( 1 );
}
if ( !atts.isDefined( SinkEventAttributes.CLASS ) )
{
atts.addAttribute( SinkEventAttributes.CLASS, "content" );
}
writeStartTag( contentStack.push( HtmlMarkup.DIV ), atts );
}
}
/**
* {@inheritDoc}
*/
@Override
public void content_()
{
try
{
writeEndTag( contentStack.pop() );
}
catch ( EmptyStackException ese )
{
/* do nothing if the stack is empty */
}
}
/**
* {@inheritDoc}
*/
@Override
public void footer()
{
footer( null );
}
/**
* {@inheritDoc}
*/
@Override
public void footer( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.FOOTER, atts );
}
/**
* {@inheritDoc}
*/
@Override
public void footer_()
{
writeEndTag( HtmlMarkup.FOOTER );
}
// -----------------------------------------------------------------------
//
// -----------------------------------------------------------------------
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#UL
*/
@Override
public void list()
{
list( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#UL
*/
@Override
public void list( SinkEventAttributes attributes )
{
if ( paragraphFlag )
{
// The content of element type "p" must match
// "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
// dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
paragraph_();
}
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.UL, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#UL
*/
@Override
public void list_()
{
writeEndTag( HtmlMarkup.UL );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void listItem()
{
listItem( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void listItem( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.LI, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void listItem_()
{
writeEndTag( HtmlMarkup.LI );
}
/**
* The default list style depends on the numbering.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#OL
*/
@Override
public void numberedList( int numbering )
{
numberedList( numbering, null );
}
/**
* The default list style depends on the numbering.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#OL
*/
@Override
public void numberedList( int numbering, SinkEventAttributes attributes )
{
if ( paragraphFlag )
{
// The content of element type "p" must match
// "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
// dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
paragraph_();
}
String style;
switch ( numbering )
{
case NUMBERING_UPPER_ALPHA:
style = "upper-alpha";
break;
case NUMBERING_LOWER_ALPHA:
style = "lower-alpha";
break;
case NUMBERING_UPPER_ROMAN:
style = "upper-roman";
break;
case NUMBERING_LOWER_ROMAN:
style = "lower-roman";
break;
case NUMBERING_DECIMAL:
default:
style = "decimal";
}
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
if ( atts == null )
{
atts = new SinkEventAttributeSet( 1 );
}
atts.addAttribute( Attribute.STYLE, "list-style-type: " + style );
writeStartTag( HtmlMarkup.OL, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#OL
*/
@Override
public void numberedList_()
{
writeEndTag( HtmlMarkup.OL );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void numberedListItem()
{
numberedListItem( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void numberedListItem( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.LI, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#LI
*/
@Override
public void numberedListItem_()
{
writeEndTag( HtmlMarkup.LI );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DL
*/
@Override
public void definitionList()
{
definitionList( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DL
*/
@Override
public void definitionList( SinkEventAttributes attributes )
{
if ( paragraphFlag )
{
// The content of element type "p" must match
// "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
// dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
paragraph_();
}
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.DL, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DL
*/
@Override
public void definitionList_()
{
writeEndTag( HtmlMarkup.DL );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DT
*/
@Override
public void definedTerm( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.DT, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DT
*/
@Override
public void definedTerm()
{
definedTerm( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DT
*/
@Override
public void definedTerm_()
{
writeEndTag( HtmlMarkup.DT );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DD
*/
@Override
public void definition()
{
definition( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DD
*/
@Override
public void definition( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
writeStartTag( HtmlMarkup.DD, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DD
*/
@Override
public void definition_()
{
writeEndTag( HtmlMarkup.DD );
}
/**
* {@inheritDoc}
*/
@Override
public void figure()
{
figure( null );
}
/**
* {@inheritDoc}
*/
@Override
public void figure( SinkEventAttributes attributes )
{
writeStartTag( HtmlMarkup.FIGURE, attributes );
}
/** {@inheritDoc} */
@Override
public void figure_()
{
writeEndTag( HtmlMarkup.FIGURE );
}
/** {@inheritDoc} */
@Override
public void figureGraphics( String name )
{
figureGraphics( name, null );
}
/** {@inheritDoc} */
@Override
public void figureGraphics( String src, SinkEventAttributes attributes )
{
MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, SinkUtils.SINK_IMG_ATTRIBUTES );
if ( filtered != null )
{
filtered.removeAttribute( Attribute.SRC.toString() );
}
int count = ( attributes == null ? 1 : attributes.getAttributeCount() + 1 );
MutableAttributeSet atts = new SinkEventAttributeSet( count );
atts.addAttribute( Attribute.SRC, escapeHTML( src ) );
atts.addAttributes( filtered );
if ( atts.getAttribute( Attribute.ALT.toString() ) == null )
{
atts.addAttribute( Attribute.ALT.toString(), "" );
}
writeStartTag( HtmlMarkup.IMG, atts, true );
}
/**
* {@inheritDoc}
*/
@Override
public void figureCaption()
{
figureCaption( null );
}
/** {@inheritDoc} */
@Override
public void figureCaption( SinkEventAttributes attributes )
{
writeStartTag( HtmlMarkup.FIGCAPTION, attributes );
}
/** {@inheritDoc} */
@Override
public void figureCaption_()
{
writeEndTag( HtmlMarkup.FIGCAPTION );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#P
*/
@Override
public void paragraph()
{
paragraph( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#P
*/
@Override
public void paragraph( SinkEventAttributes attributes )
{
paragraphFlag = true;
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.P, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#P
*/
@Override
public void paragraph_()
{
if ( paragraphFlag )
{
writeEndTag( HtmlMarkup.P );
paragraphFlag = false;
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DATA
*/
@Override
public void data( String value )
{
data( value, null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DATA
*/
@Override
public void data( String value, SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
MutableAttributeSet att = new SinkEventAttributeSet();
if ( value != null )
{
att.addAttribute( Attribute.VALUE, value );
}
att.addAttributes( atts );
writeStartTag( HtmlMarkup.DATA, att );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DATA
*/
@Override
public void data_()
{
writeEndTag( HtmlMarkup.DATA );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TIME
*/
@Override
public void time( String datetime )
{
time( datetime, null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DATA
*/
@Override
public void time( String datetime, SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
MutableAttributeSet att = new SinkEventAttributeSet();
if ( datetime != null )
{
att.addAttribute( "datetime", datetime );
}
att.addAttributes( atts );
writeStartTag( HtmlMarkup.TIME, att );
}
/**
* {@inheritDoc}
*/
@Override
public void time_()
{
writeEndTag( HtmlMarkup.TIME );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#ADDRESS
*/
@Override
public void address()
{
address( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#ADDRESS
*/
@Override
public void address( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.ADDRESS, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#ADDRESS
*/
@Override
public void address_()
{
writeEndTag( HtmlMarkup.ADDRESS );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE
*/
@Override
public void blockquote()
{
blockquote( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE
*/
@Override
public void blockquote( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.BLOCKQUOTE, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE
*/
@Override
public void blockquote_()
{
writeEndTag( HtmlMarkup.BLOCKQUOTE );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
*/
@Override
public void division()
{
division( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
*/
@Override
public void division( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.DIV, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
*/
@Override
public void division_()
{
writeEndTag( HtmlMarkup.DIV );
}
/**
* The default class style for boxed is <code>source</code>.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
* @see javax.swing.text.html.HTML.Tag#PRE
*/
@Override
public void verbatim( boolean boxed )
{
if ( boxed )
{
verbatim( SinkEventAttributeSet.BOXED );
}
else
{
verbatim( null );
}
}
/**
* The default class style for boxed is <code>source</code>.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
* @see javax.swing.text.html.HTML.Tag#PRE
*/
@Override
public void verbatim( SinkEventAttributes attributes )
{
if ( paragraphFlag )
{
// The content of element type "p" must match
// "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
// dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
paragraph_();
}
verbatimFlag = true;
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES );
if ( atts == null )
{
atts = new SinkEventAttributeSet();
}
boolean boxed = false;
if ( atts.isDefined( SinkEventAttributes.DECORATION ) )
{
boxed =
"boxed".equals( atts.getAttribute( SinkEventAttributes.DECORATION ).toString() );
}
SinkEventAttributes divAtts = null;
if ( boxed )
{
divAtts = new SinkEventAttributeSet( new String[] { Attribute.CLASS.toString(), "source" } );
}
atts.removeAttribute( SinkEventAttributes.DECORATION );
writeStartTag( HtmlMarkup.DIV, divAtts );
writeStartTag( HtmlMarkup.PRE, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#DIV
* @see javax.swing.text.html.HTML.Tag#PRE
*/
@Override
public void verbatim_()
{
writeEndTag( HtmlMarkup.PRE );
writeEndTag( HtmlMarkup.DIV );
verbatimFlag = false;
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#HR
*/
@Override
public void horizontalRule()
{
horizontalRule( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#HR
*/
@Override
public void horizontalRule( SinkEventAttributes attributes )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_HR_ATTRIBUTES );
writeSimpleTag( HtmlMarkup.HR, atts );
}
/** {@inheritDoc} */
@Override
public void table()
{
// start table with tableRows
table( null );
}
/** {@inheritDoc} */
@Override
public void table( SinkEventAttributes attributes )
{
this.tableContentWriterStack.addLast( new StringWriter() );
this.tableRows = false;
if ( paragraphFlag )
{
// The content of element type "p" must match
// "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
// dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
paragraph_();
}
// start table with tableRows
if ( attributes == null )
{
this.tableAttributes = new SinkEventAttributeSet( 0 );
}
else
{
this.tableAttributes = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_TABLE_ATTRIBUTES );
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TABLE
*/
@Override
public void table_()
{
this.tableRows = false;
writeEndTag( HtmlMarkup.TABLE );
if ( !this.cellCountStack.isEmpty() )
{
this.cellCountStack.removeLast().toString();
}
if ( this.tableContentWriterStack.isEmpty() )
{
if ( getLog().isWarnEnabled() )
{
getLog().warn( "No table content." );
}
return;
}
String tableContent = this.tableContentWriterStack.removeLast().toString();
String tableCaption = null;
if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
{
tableCaption = this.tableCaptionStack.removeLast().toString();
}
if ( tableCaption != null )
{
// DOXIA-177
StringBuilder sb = new StringBuilder();
sb.append( tableContent.substring( 0, tableContent.indexOf( Markup.GREATER_THAN ) + 1 ) );
sb.append( tableCaption );
sb.append( tableContent.substring( tableContent.indexOf( Markup.GREATER_THAN ) + 1 ) );
write( sb.toString() );
}
else
{
write( tableContent );
}
}
/**
* The default class style is <code>bodyTable</code>.
* The default align is <code>center</code>.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TABLE
*/
@Override
public void tableRows( int[] justification, boolean grid )
{
this.tableRows = true;
setCellJustif( justification );
if ( this.tableAttributes == null )
{
this.tableAttributes = new SinkEventAttributeSet( 0 );
}
MutableAttributeSet att = new SinkEventAttributeSet();
if ( !this.tableAttributes.isDefined( Attribute.BORDER.toString() ) )
{
att.addAttribute( Attribute.BORDER, ( grid ? "1" : "0" ) );
}
if ( !this.tableAttributes.isDefined( Attribute.CLASS.toString() ) )
{
att.addAttribute( Attribute.CLASS, "bodyTable" );
}
att.addAttributes( this.tableAttributes );
this.tableAttributes.removeAttributes( this.tableAttributes );
writeStartTag( HtmlMarkup.TABLE, att );
this.cellCountStack.addLast( Integer.valueOf( 0 ) );
}
/** {@inheritDoc} */
@Override
public void tableRows_()
{
this.tableRows = false;
if ( !this.cellJustifStack.isEmpty() )
{
this.cellJustifStack.removeLast();
}
if ( !this.isCellJustifStack.isEmpty() )
{
this.isCellJustifStack.removeLast();
}
this.evenTableRow = true;
}
/**
* The default class style is <code>a</code> or <code>b</code> depending the row id.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TR
*/
@Override
public void tableRow()
{
// To be backward compatible
if ( !this.tableRows )
{
tableRows( null, false );
}
tableRow( null );
}
/**
* The default class style is <code>a</code> or <code>b</code> depending the row id.
*
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TR
*/
@Override
public void tableRow( SinkEventAttributes attributes )
{
MutableAttributeSet att = new SinkEventAttributeSet();
if ( evenTableRow )
{
att.addAttribute( Attribute.CLASS, "a" );
}
else
{
att.addAttribute( Attribute.CLASS, "b" );
}
att.addAttributes( SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_TR_ATTRIBUTES ) );
writeStartTag( HtmlMarkup.TR, att );
evenTableRow = !evenTableRow;
if ( !this.cellCountStack.isEmpty() )
{
this.cellCountStack.removeLast();
this.cellCountStack.addLast( Integer.valueOf( 0 ) );
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#TR
*/
@Override
public void tableRow_()
{
writeEndTag( HtmlMarkup.TR );
}
/** {@inheritDoc} */
@Override
public void tableCell()
{
tableCell( (SinkEventAttributeSet) null );
}
/** {@inheritDoc} */
@Override
public void tableHeaderCell()
{
tableHeaderCell( (SinkEventAttributeSet) null );
}
/** {@inheritDoc} */
@Override
public void tableCell( String width )
{
MutableAttributeSet att = new SinkEventAttributeSet();
att.addAttribute( Attribute.WIDTH, width );
tableCell( false, att );
}
/** {@inheritDoc} */
@Override
public void tableHeaderCell( String width )
{
MutableAttributeSet att = new SinkEventAttributeSet();
att.addAttribute( Attribute.WIDTH, width );
tableCell( true, att );
}
/** {@inheritDoc} */
@Override
public void tableCell( SinkEventAttributes attributes )
{
tableCell( false, attributes );
}
/** {@inheritDoc} */
@Override
public void tableHeaderCell( SinkEventAttributes attributes )
{
tableCell( true, attributes );
}
/**
* @param headerRow true if it is an header row
* @param attributes the cell attributes
* @see javax.swing.text.html.HTML.Tag#TH
* @see javax.swing.text.html.HTML.Tag#TD
*/
private void tableCell( boolean headerRow, MutableAttributeSet attributes )
{
Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
if ( attributes == null )
{
writeStartTag( t, null );
}
else
{
writeStartTag( t,
SinkUtils.filterAttributes( attributes, SinkUtils.SINK_TD_ATTRIBUTES ) );
}
}
/** {@inheritDoc} */
@Override
public void tableCell_()
{
tableCell_( false );
}
/** {@inheritDoc} */
@Override
public void tableHeaderCell_()
{
tableCell_( true );
}
/**
* Ends a table cell.
*
* @param headerRow true if it is an header row
* @see javax.swing.text.html.HTML.Tag#TH
* @see javax.swing.text.html.HTML.Tag#TD
*/
private void tableCell_( boolean headerRow )
{
Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
writeEndTag( t );
if ( !this.isCellJustifStack.isEmpty() && this.isCellJustifStack.getLast().equals( Boolean.TRUE )
&& !this.cellCountStack.isEmpty() )
{
int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
this.cellCountStack.addLast( Integer.valueOf( ++cellCount ) );
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#CAPTION
*/
@Override
public void tableCaption()
{
tableCaption( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#CAPTION
*/
@Override
public void tableCaption( SinkEventAttributes attributes )
{
StringWriter sw = new StringWriter();
this.tableCaptionWriterStack.addLast( sw );
this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
// TODO: tableCaption should be written before tableRows (DOXIA-177)
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_SECTION_ATTRIBUTES );
writeStartTag( HtmlMarkup.CAPTION, atts );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#CAPTION
*/
@Override
public void tableCaption_()
{
writeEndTag( HtmlMarkup.CAPTION );
if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
{
this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
this.tableCaptionXMLWriterStack.removeLast();
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#A
*/
@Override
public void anchor( String name )
{
anchor( name, null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#A
*/
@Override
public void anchor( String name, SinkEventAttributes attributes )
{
if ( name == null )
{
throw new NullPointerException( "Anchor name cannot be null!" );
}
if ( headFlag )
{
return;
}
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BASE_ATTRIBUTES );
String id = name;
if ( !DoxiaUtils.isValidId( id ) )
{
id = DoxiaUtils.encodeId( name, true );
String msg = "Modified invalid anchor name: '" + name + "' to '" + id + "'";
logMessage( "modifiedLink", msg );
}
MutableAttributeSet att = new SinkEventAttributeSet();
att.addAttribute( Attribute.NAME, id );
att.addAttributes( atts );
writeStartTag( HtmlMarkup.A, att );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#A
*/
@Override
public void anchor_()
{
if ( !headFlag )
{
writeEndTag( HtmlMarkup.A );
}
}
/** {@inheritDoc} */
@Override
public void link( String name )
{
link( name, null );
}
/** {@inheritDoc} */
@Override
public void link( String name, SinkEventAttributes attributes )
{
if ( attributes == null )
{
link( name, null, null );
}
else
{
String target = (String) attributes.getAttribute( Attribute.TARGET.toString() );
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_LINK_ATTRIBUTES );
link( name, target, atts );
}
}
/**
* Adds a link with an optional target.
* The default style class for external link is <code>externalLink</code>.
*
* @param href the link href.
* @param target the link target, may be null.
* @param attributes an AttributeSet, may be null.
* This is supposed to be filtered already.
* @see javax.swing.text.html.HTML.Tag#A
*/
private void link( String href, String target, MutableAttributeSet attributes )
{
if ( href == null )
{
throw new NullPointerException( "Link name cannot be null!" );
}
if ( headFlag )
{
return;
}
MutableAttributeSet att = new SinkEventAttributeSet();
if ( DoxiaUtils.isExternalLink( href ) )
{
att.addAttribute( Attribute.CLASS, "externalLink" );
}
att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( href ) );
if ( target != null )
{
att.addAttribute( Attribute.TARGET, target );
}
if ( attributes != null )
{
attributes.removeAttribute( Attribute.HREF.toString() );
attributes.removeAttribute( Attribute.TARGET.toString() );
att.addAttributes( attributes );
}
writeStartTag( HtmlMarkup.A, att );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#A
*/
@Override
public void link_()
{
if ( !headFlag )
{
writeEndTag( HtmlMarkup.A );
}
}
/** {@inheritDoc} */
@Override
public void inline()
{
inline( null );
}
private void inlineSemantics( SinkEventAttributes attributes, String semantic,
List<Tag> tags, Tag tag )
{
if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, semantic ) )
{
writeStartTag( tag );
tags.add( 0, tag );
}
}
/** {@inheritDoc} */
@Override
public void inline( SinkEventAttributes attributes )
{
if ( !headFlag )
{
List<Tag> tags = new ArrayList<Tag>();
if ( attributes != null )
{
inlineSemantics( attributes, "emphasis", tags, HtmlMarkup.EM );
inlineSemantics( attributes, "strong", tags, HtmlMarkup.STRONG );
inlineSemantics( attributes, "small", tags, HtmlMarkup.SMALL );
inlineSemantics( attributes, "line-through", tags, HtmlMarkup.S );
inlineSemantics( attributes, "citation", tags, HtmlMarkup.CITE );
inlineSemantics( attributes, "quote", tags, HtmlMarkup.Q );
inlineSemantics( attributes, "definition", tags, HtmlMarkup.DFN );
inlineSemantics( attributes, "abbreviation", tags, HtmlMarkup.ABBR );
inlineSemantics( attributes, "italic", tags, HtmlMarkup.I );
inlineSemantics( attributes, "bold", tags, HtmlMarkup.B );
inlineSemantics( attributes, "code", tags, HtmlMarkup.CODE );
inlineSemantics( attributes, "variable", tags, HtmlMarkup.VAR );
inlineSemantics( attributes, "sample", tags, HtmlMarkup.SAMP );
inlineSemantics( attributes, "keyboard", tags, HtmlMarkup.KBD );
inlineSemantics( attributes, "superscript", tags, HtmlMarkup.SUP );
inlineSemantics( attributes, "subscript", tags, HtmlMarkup.SUB );
inlineSemantics( attributes, "annotation", tags, HtmlMarkup.U );
inlineSemantics( attributes, "highlight", tags, HtmlMarkup.MARK );
inlineSemantics( attributes, "ruby", tags, HtmlMarkup.RUBY );
inlineSemantics( attributes, "rubyBase", tags, HtmlMarkup.RB );
inlineSemantics( attributes, "rubyText", tags, HtmlMarkup.RT );
inlineSemantics( attributes, "rubyTextContainer", tags, HtmlMarkup.RTC );
inlineSemantics( attributes, "rubyParentheses", tags, HtmlMarkup.RP );
inlineSemantics( attributes, "bidirectionalIsolation", tags, HtmlMarkup.BDI );
inlineSemantics( attributes, "bidirectionalOverride", tags, HtmlMarkup.BDO );
inlineSemantics( attributes, "phrase", tags, HtmlMarkup.SPAN );
inlineSemantics( attributes, "insert", tags, HtmlMarkup.INS );
inlineSemantics( attributes, "delete", tags, HtmlMarkup.DEL );
}
inlineStack.push( tags );
}
}
/** {@inheritDoc} */
@Override
public void inline_()
{
if ( !headFlag )
{
for ( Tag tag: inlineStack.pop() )
{
writeEndTag( tag );
}
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#I
*/
@Override
public void italic()
{
inline( SinkEventAttributeSet.Semantics.ITALIC );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#I
*/
@Override
public void italic_()
{
inline_();
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#B
*/
@Override
public void bold()
{
inline( SinkEventAttributeSet.Semantics.BOLD );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#B
*/
@Override
public void bold_()
{
inline_();
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#CODE
*/
@Override
public void monospaced()
{
inline( SinkEventAttributeSet.Semantics.CODE );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#CODE
*/
@Override
public void monospaced_()
{
inline_();
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#BR
*/
@Override
public void lineBreak()
{
lineBreak( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#BR
*/
@Override
public void lineBreak( SinkEventAttributes attributes )
{
if ( headFlag || isVerbatimFlag() )
{
getTextBuffer().append( EOL );
}
else
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BR_ATTRIBUTES );
writeSimpleTag( HtmlMarkup.BR, atts );
}
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#WBR
*/
@Override
public void lineBreakOpportunity()
{
lineBreakOpportunity( null );
}
/**
* {@inheritDoc}
* @see javax.swing.text.html.HTML.Tag#WBR
*/
@Override
public void lineBreakOpportunity( SinkEventAttributes attributes )
{
if ( !headFlag && !isVerbatimFlag() )
{
MutableAttributeSet atts = SinkUtils.filterAttributes(
attributes, SinkUtils.SINK_BR_ATTRIBUTES );
writeSimpleTag( HtmlMarkup.WBR, atts );
}
}
/** {@inheritDoc} */
@Override
public void pageBreak()
{
comment( " PB " );
}
/** {@inheritDoc} */
@Override
public void nonBreakingSpace()
{
if ( headFlag )
{
getTextBuffer().append( ' ' );
}
else
{
write( "&#160;" );
}
}
/** {@inheritDoc} */
@Override
public void text( String text )
{
if ( headFlag )
{
getTextBuffer().append( text );
}
else if ( verbatimFlag )
{
verbatimContent( text );
}
else
{
content( text );
}
}
/** {@inheritDoc} */
@Override
public void text( String text, SinkEventAttributes attributes )
{
text( text );
}
/** {@inheritDoc} */
@Override
public void rawText( String text )
{
if ( headFlag )
{
getTextBuffer().append( text );
}
else
{
write( text );
}
}
/** {@inheritDoc} */
@Override
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 ) )
{
getLog().warn( "[Xhtml5 Sink] Modified invalid comment '" + originalComment
+ "' to '" + comment + "'" );
}
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() );
}
}
/**
* Add an unknown event.
* This can be used to generate html tags for which no corresponding sink event exists.
*
* <p>
* If {@link org.apache.maven.doxia.util.HtmlTools#getHtmlTag(String) HtmlTools.getHtmlTag( name )}
* does not return null, the corresponding tag will be written.
* </p>
*
* <p>For example, the div block</p>
*
* <pre>
* &lt;div class="detail" style="display:inline"&gt;text&lt;/div&gt;
* </pre>
*
* <p>can be generated via the following event sequence:</p>
*
* <pre>
* SinkEventAttributeSet atts = new SinkEventAttributeSet();
* atts.addAttribute( SinkEventAttributes.CLASS, "detail" );
* atts.addAttribute( SinkEventAttributes.STYLE, "display:inline" );
* sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
* sink.text( "text" );
* sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
* </pre>
*
* @param name the name of the event. If this is not a valid xhtml tag name
* as defined in {@link org.apache.maven.doxia.markup.HtmlMarkup} then the event is ignored.
* @param requiredParams If this is null or the first argument is not an Integer then the event is ignored.
* The first argument should indicate the type of the unknown event, its integer value should be one of
* {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_START TAG_TYPE_START},
* {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_END TAG_TYPE_END},
* {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_SIMPLE TAG_TYPE_SIMPLE},
* {@link org.apache.maven.doxia.markup.HtmlMarkup#ENTITY_TYPE ENTITY_TYPE}, or
* {@link org.apache.maven.doxia.markup.HtmlMarkup#CDATA_TYPE CDATA_TYPE},
* otherwise the event will be ignored.
* @param attributes a set of attributes for the event. May be null.
* The attributes will always be written, no validity check is performed.
*/
@Override
public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
{
if ( requiredParams == null || !( requiredParams[0] instanceof Integer ) )
{
String msg = "No type information for unknown event: '" + name + "', ignoring!";
logMessage( "noTypeInfo", msg );
return;
}
int tagType = ( (Integer) requiredParams[0] ).intValue();
if ( tagType == ENTITY_TYPE )
{
rawText( name );
return;
}
if ( tagType == CDATA_TYPE )
{
rawText( EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL );
return;
}
Tag tag = HtmlTools.getHtmlTag( name );
if ( tag == null )
{
String msg = "No HTML tag found for unknown event: '" + name + "', ignoring!";
logMessage( "noHtmlTag", msg );
}
else
{
if ( tagType == TAG_TYPE_SIMPLE )
{
writeSimpleTag( tag, escapeAttributeValues( attributes ) );
}
else if ( tagType == TAG_TYPE_START )
{
writeStartTag( tag, escapeAttributeValues( attributes ) );
}
else if ( tagType == TAG_TYPE_END )
{
writeEndTag( tag );
}
else
{
String msg = "No type information for unknown event: '" + name + "', ignoring!";
logMessage( "noTypeInfo", msg );
}
}
}
private SinkEventAttributes escapeAttributeValues( SinkEventAttributes attributes )
{
SinkEventAttributeSet set = new SinkEventAttributeSet( attributes.getAttributeCount() );
Enumeration<?> names = attributes.getAttributeNames();
while ( names.hasMoreElements() )
{
Object name = names.nextElement();
set.addAttribute( name, escapeHTML( attributes.getAttribute( name ).toString() ) );
}
return set;
}
/** {@inheritDoc} */
@Override
public void flush()
{
writer.flush();
}
/** {@inheritDoc} */
@Override
public void close()
{
writer.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();
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
/**
* Write HTML escaped text to output.
*
* @param text The text to write.
*/
protected void content( String text )
{
// small hack due to DOXIA-314
String txt = escapeHTML( text );
txt = StringUtils.replace( txt, "&amp;#", "&#" );
write( txt );
}
/**
* Write HTML escaped text to output.
*
* @param text The text to write.
*/
protected void verbatimContent( String text )
{
write( escapeHTML( text ) );
}
/**
* Forward to HtmlTools.escapeHTML( text ).
*
* @param text the String to escape, may be null
* @return the text escaped, "" if null String input
* @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String)
*/
protected static String escapeHTML( String text )
{
return HtmlTools.escapeHTML( text, false );
}
/**
* Forward to HtmlTools.encodeURL( text ).
*
* @param text the String to encode, may be null.
* @return the text encoded, null if null String input.
* @see org.apache.maven.doxia.util.HtmlTools#encodeURL(String)
*/
protected static String encodeURL( String text )
{
return HtmlTools.encodeURL( text );
}
/** {@inheritDoc} */
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
{
writer.write( unifyEOLs( text ) );
}
}
/** {@inheritDoc} */
@Override
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} */
@Override
protected void writeEndTag( Tag t )
{
if ( this.tableCaptionXMLWriterStack.isEmpty() )
{
super.writeEndTag( t );
}
else
{
this.tableCaptionXMLWriterStack.getLast().endElement();
}
}
/**
* 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
*/
private void logMessage( String key, String msg )
{
final String mesg = "[XHTML5 Sink] " + msg;
if ( getLog().isDebugEnabled() )
{
getLog().debug( mesg );
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( mesg );
warnMessages.put( key, set );
}
}