blob: 32f062fce528787b45e6106f98a7cafa1e0bb72c [file] [log] [blame]
package org.apache.maven.doxia.module.xdoc;
/*
* 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.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import javax.swing.text.html.HTML.Attribute;
import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.macro.MacroRequest;
import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.parser.XhtmlBaseParser;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
import org.apache.maven.doxia.util.HtmlTools;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Parse an xdoc model and emit events into the specified doxia Sink.
*
* @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
* @version $Id$
* @since 1.0
*/
@Component( role = Parser.class, hint = "xdoc" )
public class XdocParser
extends XhtmlBaseParser
implements XdocMarkup
{
/**
* The source content of the input reader. Used to pass into macros.
*/
private String sourceContent;
/**
* Empty elements don't write a closing tag.
*/
private boolean isEmptyElement;
/**
* A macro name.
*/
private String macroName;
/**
* The macro parameters.
*/
private Map<String, Object> macroParameters = new HashMap<String, Object>();
/**
* Indicates that we're inside &lt;properties&gt; or &lt;head&gt;.
*/
private boolean inHead;
/**
* Indicates that &lt;title&gt; was called from &lt;properties&gt; or &lt;head&gt;.
*/
private boolean hasTitle;
/**
* {@inheritDoc}
*/
public void parse( Reader source, Sink sink )
throws ParseException
{
this.sourceContent = null;
try
{
StringWriter contentWriter = new StringWriter();
IOUtil.copy( source, contentWriter );
sourceContent = contentWriter.toString();
}
catch ( IOException ex )
{
throw new ParseException( "Error reading the input source: " + ex.getMessage(), ex );
}
finally
{
IOUtil.close( source );
}
// leave this at default (false) until everything is properly implemented, see DOXIA-226
//setIgnorableWhitespace( true );
try
{
super.parse( new StringReader( sourceContent ), sink );
}
finally
{
this.sourceContent = null;
}
}
/**
* {@inheritDoc}
*/
protected void handleStartTag( XmlPullParser parser, Sink sink )
throws XmlPullParserException, MacroExecutionException
{
isEmptyElement = parser.isEmptyElementTag();
SinkEventAttributeSet attribs = getAttributesFromParser( parser );
if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
{
//Do nothing
return;
}
else if ( parser.getName().equals( HEAD.toString() ) )
{
if ( !inHead ) // we might be in head from a <properties> already
{
this.inHead = true;
sink.head( attribs );
}
}
else if ( parser.getName().equals( TITLE.toString() ) )
{
if ( hasTitle )
{
getLog().warn( "<title> was already defined in <properties>, ignored <title> in <head>." );
try
{
parser.nextText(); // ignore next text event
}
catch ( IOException ex )
{
throw new XmlPullParserException( "Failed to parse text", parser, ex );
}
}
else
{
sink.title( attribs );
}
}
else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
{
sink.author( attribs );
}
else if ( parser.getName().equals( DATE_TAG.toString() ) )
{
sink.date( attribs );
}
else if ( parser.getName().equals( META.toString() ) )
{
handleMetaStart( parser, sink, attribs );
}
else if ( parser.getName().equals( BODY.toString() ) )
{
if ( inHead )
{
sink.head_();
this.inHead = false;
}
sink.body( attribs );
}
else if ( parser.getName().equals( SECTION_TAG.toString() ) )
{
handleSectionStart( Sink.SECTION_LEVEL_1, sink, attribs, parser );
}
else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
{
handleSectionStart( Sink.SECTION_LEVEL_2, sink, attribs, parser );
}
else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
{
verbatim();
attribs.addAttributes( SinkEventAttributeSet.BOXED );
sink.verbatim( attribs );
}
else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
{
if ( !inHead ) // we might be in head from a <head> already
{
this.inHead = true;
sink.head( attribs );
}
}
// ----------------------------------------------------------------------
// Macro
// ----------------------------------------------------------------------
else if ( parser.getName().equals( MACRO_TAG.toString() ) )
{
handleMacroStart( parser );
}
else if ( parser.getName().equals( PARAM.toString() ) )
{
handleParamStart( parser, sink );
}
else if ( !baseStartTag( parser, sink ) )
{
if ( isEmptyElement )
{
handleUnknown( parser, sink, TAG_TYPE_SIMPLE );
}
else
{
handleUnknown( parser, sink, TAG_TYPE_START );
}
if ( getLog().isDebugEnabled() )
{
String position = "[" + parser.getLineNumber() + ":" + parser.getColumnNumber() + "]";
String tag = "<" + parser.getName() + ">";
getLog().debug( "Unrecognized xdoc tag: " + tag + " at " + position );
}
}
}
/**
* {@inheritDoc}
*/
protected void handleEndTag( XmlPullParser parser, Sink sink )
throws XmlPullParserException, MacroExecutionException
{
if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
{
//Do nothing
return;
}
else if ( parser.getName().equals( HEAD.toString() ) )
{
//Do nothing, head is closed with BODY start.
}
else if ( parser.getName().equals( BODY.toString() ) )
{
consecutiveSections( 0, sink );
sink.body_();
}
else if ( parser.getName().equals( TITLE.toString() ) )
{
if ( !hasTitle )
{
sink.title_();
this.hasTitle = true;
}
}
else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
{
sink.author_();
}
else if ( parser.getName().equals( DATE_TAG.toString() ) )
{
sink.date_();
}
else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
{
verbatim_();
sink.verbatim_();
}
else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
{
//Do nothing, head is closed with BODY start.
}
else if ( parser.getName().equals( MACRO_TAG.toString() ) )
{
handleMacroEnd( sink );
}
else if ( parser.getName().equals( PARAM.toString() ) )
{
if ( !StringUtils.isNotEmpty( macroName ) )
{
handleUnknown( parser, sink, TAG_TYPE_END );
}
}
else if ( parser.getName().equals( SECTION_TAG.toString() ) )
{
consecutiveSections( 0, sink );
sink.section1_();
}
else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
{
consecutiveSections( Sink.SECTION_LEVEL_1, sink );
}
else if ( !baseEndTag( parser, sink ) )
{
if ( !isEmptyElement )
{
handleUnknown( parser, sink, TAG_TYPE_END );
}
}
isEmptyElement = false;
}
/**
* {@inheritDoc}
*/
protected void consecutiveSections( int newLevel, Sink sink )
{
closeOpenSections( newLevel, sink );
openMissingSections( newLevel, sink );
setSectionLevel( newLevel );
}
/**
* {@inheritDoc}
*/
protected void init()
{
super.init();
this.isEmptyElement = false;
this.macroName = null;
this.macroParameters = null;
this.inHead = false;
this.hasTitle = false;
}
/**
* Close open h4, h5, h6 sections.
*/
private void closeOpenSections( int newLevel, Sink sink )
{
while ( getSectionLevel() >= newLevel )
{
if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
{
sink.section5_();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
{
sink.section4_();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
{
sink.section3_();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
{
sink.section2_();
}
setSectionLevel( getSectionLevel() - 1 );
}
}
private void handleMacroEnd( Sink sink )
throws MacroExecutionException
{
if ( !isSecondParsing() && StringUtils.isNotEmpty( macroName ) )
{
MacroRequest request =
new MacroRequest( sourceContent, new XdocParser(), macroParameters, getBasedir() );
try
{
executeMacro( macroName, request, sink );
}
catch ( MacroNotFoundException me )
{
throw new MacroExecutionException( "Macro not found: " + macroName, me );
}
}
// Reinit macro
macroName = null;
macroParameters = null;
}
private void handleMacroStart( XmlPullParser parser )
throws MacroExecutionException
{
if ( !isSecondParsing() )
{
macroName = parser.getAttributeValue( null, Attribute.NAME.toString() );
if ( macroParameters == null )
{
macroParameters = new HashMap<String, Object>();
}
if ( StringUtils.isEmpty( macroName ) )
{
throw new MacroExecutionException(
"The '" + Attribute.NAME.toString() + "' attribute for the '" + MACRO_TAG.toString()
+ "' tag is required." );
}
}
}
private void handleMetaStart( XmlPullParser parser, Sink sink, SinkEventAttributeSet attribs )
{
String name = parser.getAttributeValue( null, Attribute.NAME.toString() );
String content = parser.getAttributeValue( null, Attribute.CONTENT.toString() );
if ( "author".equals( name ) )
{
sink.author( null );
sink.text( content );
sink.author_();
}
else if ( "date".equals( name ) )
{
sink.date( null );
sink.text( content );
sink.date_();
}
else
{
sink.unknown( "meta", new Object[]{ Integer.valueOf( TAG_TYPE_SIMPLE ) }, attribs );
}
}
private void handleParamStart( XmlPullParser parser, Sink sink )
throws MacroExecutionException
{
if ( !isSecondParsing() )
{
if ( StringUtils.isNotEmpty( macroName ) )
{
String paramName = parser.getAttributeValue( null, Attribute.NAME.toString() );
String paramValue = parser.getAttributeValue( null, Attribute.VALUE.toString() );
if ( StringUtils.isEmpty( paramName ) || StringUtils.isEmpty( paramValue ) )
{
throw new MacroExecutionException(
"'" + Attribute.NAME.toString() + "' and '" + Attribute.VALUE.toString()
+ "' attributes for the '" + PARAM.toString() + "' tag are required inside the '"
+ MACRO_TAG.toString() + "' tag." );
}
macroParameters.put( paramName, paramValue );
}
else
{
// param tag from non-macro object, see MSITE-288
handleUnknown( parser, sink, TAG_TYPE_START );
}
}
}
private void handleSectionStart( int level, Sink sink, SinkEventAttributeSet attribs, XmlPullParser parser )
{
consecutiveSections( level, sink );
Object id = attribs.getAttribute( Attribute.ID.toString() );
if ( id != null )
{
sink.anchor( id.toString() );
sink.anchor_();
}
sink.section( level, attribs );
sink.sectionTitle( level, null );
sink.text( HtmlTools.unescapeHTML( parser.getAttributeValue( null, Attribute.NAME.toString() ) ) );
sink.sectionTitle_( level );
}
/**
* Open missing h4, h5, h6 sections.
*/
private void openMissingSections( int newLevel, Sink sink )
{
while ( getSectionLevel() < newLevel - 1 )
{
setSectionLevel( getSectionLevel() + 1 );
if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
{
sink.section5();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
{
sink.section4();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
{
sink.section3();
}
else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
{
sink.section2();
}
}
}
}