blob: 97165824b62f76600b7ad73b0315ef0f20ddb3c8 [file] [log] [blame]
package org.apache.maven.doxia.module.twiki.parser;
/*
* 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 org.apache.maven.doxia.util.ByLineSource;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.sink.Sink;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Generic list parser
*
* @author Juan F. Codagnone
* @version $Id$
*/
public class GenericListBlockParser
implements BlockParser
{
static final String EOL = System.getProperty( "line.separator" );
/**
* parser used to create text blocks
*/
private FormatedTextParser formatedTextParser;
/**
* supported patterns
*/
private final Pattern[] patterns = new Pattern[TYPES.length];
/**
* Creates the GenericListBlockParser.
*/
public GenericListBlockParser()
{
for ( int i = 0; i < TYPES.length; i++ )
{
patterns[i] = Pattern.compile( "^(( )+)" + TYPES[i].getItemPattern() + "(.*)$" );
}
}
/** {@inheritDoc} */
public final boolean accept( final String line )
{
boolean ret = false;
for ( int i = 0; !ret && i < patterns.length; i++ )
{
ret |= patterns[i].matcher( line ).lookingAt();
}
return ret;
}
/**
* {@inheritDoc}
*/
public final Block visit( final String line, final ByLineSource source )
throws ParseException
{
final TreeListBuilder treeListBuilder = new TreeListBuilder( formatedTextParser );
// new TreeListBuilder(formatedTextParser);
String l = line;
do
{
if ( !accept( l ) )
{
break;
}
for ( int i = 0; i < patterns.length; i++ )
{
final Matcher m = patterns[i].matcher( l );
if ( m.lookingAt() )
{
final int numberOfSpaces = 3;
final int textGroup = 3;
assert m.group( 1 ).length() % numberOfSpaces == 0;
final int level = m.group( 1 ).length() / numberOfSpaces;
treeListBuilder.feedEntry( TYPES[i], level, m.group( textGroup ).trim() );
break;
}
}
}
while ( ( l = source.getNextLine() ) != null );
if ( l != null )
{
source.ungetLine();
}
return treeListBuilder.getBlock();
}
/**
* Sets the formatTextParser.
*
* @param textParser <code>FormatedTextParser</code> with the formatTextParser.
*/
public final void setTextParser( final FormatedTextParser textParser )
{
if ( textParser == null )
{
throw new IllegalArgumentException( "formatTextParser can't be null" );
}
this.formatedTextParser = textParser;
}
interface Type
{
/**
* @return the pattern of the item part of the list regex
*/
String getItemPattern();
/**
* @param items children of the new listblock
* @return a new ListBlock
*/
ListBlock createList( final ListItemBlock[] items );
}
/**
* unordered list
*/
private static final Type LIST = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[*]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new UnorderedListBlock( items );
}
};
/**
* a.
*/
private static final Type ORDERED_LOWER_ALPHA = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[a-hj-z][.]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new NumeratedListBlock( Sink.NUMBERING_LOWER_ALPHA, items );
}
};
/**
* A.
*/
private static final Type ORDERED_UPPER_ALPHA = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[A-HJ-Z][.]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new NumeratedListBlock( Sink.NUMBERING_UPPER_ALPHA, items );
}
};
/**
* 1.
*/
private static final Type ORDERERED_DECIMAL = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[0123456789][.]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new NumeratedListBlock( Sink.NUMBERING_DECIMAL, items );
}
};
/**
* i.
*/
private static final Type ORDERERED_LOWER_ROMAN = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[i][.]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new NumeratedListBlock( Sink.NUMBERING_LOWER_ROMAN, items );
}
};
/**
* I.
*/
private static final Type ORDERERED_UPPER_ROMAN = new Type()
{
/** {@inheritDoc} */
public String getItemPattern()
{
return "[I][.]";
}
/** {@inheritDoc} */
public ListBlock createList( final ListItemBlock[] items )
{
return new NumeratedListBlock( Sink.NUMBERING_UPPER_ROMAN, items );
}
};
private static final Type[] TYPES =
{ LIST, ORDERED_LOWER_ALPHA, ORDERED_UPPER_ALPHA, ORDERERED_DECIMAL, ORDERERED_LOWER_ROMAN,
ORDERERED_UPPER_ROMAN };
}
/**
* It helps to build
*
* @author Juan F. Codagnone
* @version $Id$
*/
class TreeListBuilder
{
/**
* parser that create text blocks
*/
private final FormatedTextParser textParser;
/**
* tree root
*/
private final TreeComponent root;
/**
* the current element of the tree
*/
private TreeComponent current;
/**
* Creates the TreeListBuilder.
*
* @param formatTextParser parser that create text blocks
* @throws IllegalArgumentException if <code>formatTextParser</code> is null
*/
TreeListBuilder( final FormatedTextParser formatTextParser )
throws IllegalArgumentException
{
if ( formatTextParser == null )
{
throw new IllegalArgumentException( "argument is null" );
}
this.textParser = formatTextParser;
root = new TreeComponent( null, "root", null );
current = root;
}
/**
* recibe un nivel y un texto y armar magicamente (manteniendo estado)
* el �rbol
*
* @param type type of list
* @param level indentation level of the item
* @param text text of the item
*/
void feedEntry( final GenericListBlockParser.Type type, final int level, final String text )
{
final int currentDepth = current.getDepth();
final int incomingLevel = level - 1;
if ( incomingLevel == currentDepth )
{
// nothing to move
}
else if ( incomingLevel > currentDepth )
{
// el actual ahora es el �ltimo que insert�
final TreeComponent[] components = current.getChildren();
if ( components.length == 0 )
{
/* for example:
* * item1
* * item2
*/
for ( int i = 0, n = incomingLevel - currentDepth; i < n; i++ )
{
current = current.addChildren( "", type );
}
}
else
{
current = components[components.length - 1];
}
}
else
{
for ( int i = 0, n = currentDepth - incomingLevel; i < n; i++ )
{
current = current.getFather();
if ( current == null )
{
throw new IllegalStateException();
}
}
}
current.addChildren( text, type );
}
/**
* @return a Block for the list that we received
*/
ListBlock getBlock()
{
return getList( root );
}
/**
* Wrapper
*
* @param tc tree
* @return list Block for this tree
*/
private ListBlock getList( final TreeComponent tc )
{
ListItemBlock[] li = getListItems( tc ).toArray( new ListItemBlock[] {} );
return tc.getChildren()[0].getType().createList( li );
}
/**
* @param tc tree
* @return list Block for this tree
*/
private List<ListItemBlock> getListItems( final TreeComponent tc )
{
final List<ListItemBlock> blocks = new ArrayList<ListItemBlock>();
for ( int i = 0; i < tc.getChildren().length; i++ )
{
final TreeComponent child = tc.getChildren()[i];
Block[] text = new Block[] {};
if ( child.getFather() != null )
{
text = textParser.parse( child.getText() );
}
if ( child.getChildren().length != 0 )
{
blocks.add( new ListItemBlock( text, getList( child ) ) );
}
else
{
blocks.add( new ListItemBlock( text ) );
}
}
return blocks;
}
/**
* A bidirectional tree node
*
* @author Juan F. Codagnone
* @version $Id$
*/
class TreeComponent
{
/**
* childrens
*/
private List<TreeComponent> children = new ArrayList<TreeComponent>();
/**
* node text
*/
private String text;
/**
* the father
*/
private TreeComponent father;
/**
* type of the list
*/
private GenericListBlockParser.Type type;
/**
* Creates the TreeComponent.
*
* @param father Component father
* @param text component text
* @param type component type
*/
TreeComponent( final TreeComponent father, final String text, final GenericListBlockParser.Type type )
{
this.text = text;
this.father = father;
this.type = type;
}
/**
* @return my childrens
*/
TreeComponent[] getChildren()
{
return (TreeComponent[]) children.toArray( new TreeComponent[] {} );
}
/**
* adds a children node
*
* @param t text of the children
* @param ttype component type
* @return the new node created
*/
TreeComponent addChildren( final String t, final GenericListBlockParser.Type ttype )
{
if ( t == null || ttype == null )
{
throw new IllegalArgumentException( "argument is null" );
}
final TreeComponent ret = new TreeComponent( this, t, ttype );
children.add( ret );
return ret;
}
/**
* @return the father
*/
TreeComponent getFather()
{
return father;
}
/**
* @return the node depth in the tree
*/
int getDepth()
{
int ret = 0;
TreeComponent c = this;
while ( ( c = c.getFather() ) != null )
{
ret++;
}
return ret;
}
/** {@inheritDoc} */
public String toString()
{
return toString( "" );
}
/** {@inheritDoc} */
public String toString( final String indent )
{
final StringBuilder sb = new StringBuilder();
if ( father != null )
{
sb.append( indent );
sb.append( "- " );
sb.append( text );
sb.append( GenericListBlockParser.EOL );
}
for ( TreeComponent lc : children )
{
sb.append( lc.toString( indent + " " ) );
}
return sb.toString();
}
/**
* Returns the text.
*
* @return <code>String</code> with the text.
*/
String getText()
{
return text;
}
/**
* Returns the type.
*
* @return <code>Type</code> with the text.
*/
GenericListBlockParser.Type getType()
{
return type;
}
}
}