Index: src/main/java/org/apache/maven/doxia/module/confluence/parser/AbstractFatherBlock.java =================================================================== --- src/main/java/org/apache/maven/doxia/module/confluence/parser/AbstractFatherBlock.java (revision 595967) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/AbstractFatherBlock.java (working copy) @@ -19,9 +19,8 @@ * under the License. */ -import java.util.Arrays; -import java.util.List; import java.util.Iterator; +import java.util.List; import org.apache.maven.doxia.sink.Sink; Index: src/main/java/org/apache/maven/doxia/module/confluence/parser/ChildBlocksBuilder.java =================================================================== --- src/main/java/org/apache/maven/doxia/module/confluence/parser/ChildBlocksBuilder.java (revision 595967) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/ChildBlocksBuilder.java (working copy) @@ -20,37 +20,49 @@ */ import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.codehaus.plexus.util.StringUtils; /** * Re-usable builder that can be used to generate paragraph and list item text from a string containing all the content - * and wiki formatting. + * and wiki formatting. This class is intentionally stateful, but cheap to create, so create one as needed and keep it + * on the stack to preserve stateless behaviour in the caller. * * @author Dave Syer */ public class ChildBlocksBuilder { + private boolean insideBold = false; + + private boolean insideItalic = false; + + private boolean insideLink = false; + + private List blocks = new ArrayList(); + + private StringBuffer text = new StringBuffer(); + + private String input; + + private boolean insideMonospaced; + + public ChildBlocksBuilder( String input ) + { + this.input = input; + } + /** * Utility method to convert marked up content into blocks for rendering. * * @param input a String with no line breaks * @return a list of Blocks that can be used to render it */ - public List getBlocks( String input ) + public List getBlocks() { + List specialBlocks = new ArrayList(); - boolean insideBold = false; - boolean insideItalic = false; - boolean insideLink = false; - - List blocks = new ArrayList(); - - StringBuffer text = new StringBuffer(); - for ( int i = 0; i < input.length(); i++ ) { char c = input.charAt( i ); @@ -60,13 +72,13 @@ case '*': if ( insideBold ) { - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new BoldBlock( Arrays.asList( new Block[] { tb } ) ) ); + insideBold = false; + specialBlocks = getList(new BoldBlock( getChildren(text, specialBlocks) ), specialBlocks); text = new StringBuffer(); } else { - text = addTextBlockIfNecessary( blocks, text ); + text = addTextBlockIfNecessary( blocks, specialBlocks, text ); insideBold = true; } @@ -74,20 +86,20 @@ case '_': if ( insideItalic ) { - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new ItalicBlock( Arrays.asList( new Block[] { tb } ) ) ); + insideItalic = false; + specialBlocks = getList(new ItalicBlock( getChildren( text, specialBlocks ) ), specialBlocks); text = new StringBuffer(); } else { - text = addTextBlockIfNecessary( blocks, text ); + text = addTextBlockIfNecessary( blocks, specialBlocks, text ); insideItalic = true; } break; case '[': insideLink = true; - text = addTextBlockIfNecessary( blocks, text ); + text = addTextBlockIfNecessary( blocks, specialBlocks, text ); break; case ']': if ( insideLink ) @@ -113,17 +125,20 @@ } text = new StringBuffer(); + insideLink = false; } break; case '{': + text = addTextBlockIfNecessary( blocks, specialBlocks, text ); + if ( charAt( input, i ) == '{' ) // it's monospaced { i++; + insideMonospaced = true; } // else it's a confluence macro... - text = addTextBlockIfNecessary( blocks, text ); break; case '}': @@ -133,8 +148,8 @@ if ( charAt( input, i ) == '}' ) { i++; - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new MonospaceBlock( Arrays.asList( new Block[] { tb } ) ) ); + insideMonospaced = false; + specialBlocks = getList(new MonospaceBlock( getChildren( text, specialBlocks ) ), specialBlocks); text = new StringBuffer(); } else @@ -159,7 +174,7 @@ if ( charAt( input, i ) == '\\' ) { i++; - text = addTextBlockIfNecessary( blocks, text ); + text = addTextBlockIfNecessary( blocks, specialBlocks, text ); blocks.add( new LinebreakBlock() ); } else @@ -172,6 +187,16 @@ default: text.append( c ); } + + if ( !specialBlocks.isEmpty() ) + { + if ( !insideItalic && !insideBold && !insideMonospaced ) + { + blocks.addAll( specialBlocks ); + specialBlocks.clear(); + } + } + } if ( text.length() > 0 ) @@ -182,18 +207,50 @@ return blocks; } + private List getList( Block block, List currentBlocks ) + { + List list = new ArrayList(); + if (insideBold || insideItalic || insideMonospaced) { + list.addAll( currentBlocks ); + } + list.add( block ); + return list; + } + + private List getChildren( StringBuffer buffer, List currentBlocks ) + { + String text = buffer.toString().trim(); + if (currentBlocks.isEmpty() && StringUtils.isEmpty( text )) { + return new ArrayList(); + } + ArrayList list = new ArrayList(); + if (!insideBold && !insideItalic && !insideMonospaced) { + list.addAll( currentBlocks ); + } + if (StringUtils.isEmpty( text )) { + return list; + } + list.add( new TextBlock(text) ); + return list; + } + private static char charAt( String input, int i ) { return input.length() > i + 1 ? input.charAt( i + 1 ) : '\0'; } - private StringBuffer addTextBlockIfNecessary( List blocks, StringBuffer text ) + private StringBuffer addTextBlockIfNecessary( List blocks, List specialBlocks, StringBuffer text ) { if ( text.length() == 0 ) { return text; } - blocks.add( new TextBlock( text.toString() ) ); + TextBlock textBlock = new TextBlock( text.toString() ); + if (!insideBold && !insideItalic && !insideMonospaced) { + blocks.add( textBlock ); + } else { + specialBlocks.add( textBlock ); + } return new StringBuffer(); } Index: src/main/java/org/apache/maven/doxia/module/confluence/parser/list/TreeListBuilder.java =================================================================== --- src/main/java/org/apache/maven/doxia/module/confluence/parser/list/TreeListBuilder.java (revision 595967) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/list/TreeListBuilder.java (working copy) @@ -118,7 +118,7 @@ if ( child.getFather() != null ) { - childBlocks.addAll( new ChildBlocksBuilder().getBlocks( child.getText() ) ); + childBlocks.addAll( new ChildBlocksBuilder(child.getText()).getBlocks() ); } if ( child.getChildren().size() != 0 ) Index: src/main/java/org/apache/maven/doxia/module/confluence/parser/ParagraphBlockParser.java =================================================================== --- src/main/java/org/apache/maven/doxia/module/confluence/parser/ParagraphBlockParser.java (revision 595967) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/ParagraphBlockParser.java (working copy) @@ -43,8 +43,8 @@ throws ParseException { - ChildBlocksBuilder builder = new ChildBlocksBuilder(); - return new ParagraphBlock( builder.getBlocks( appendUntilEmptyLine( line, source ) ) ); + ChildBlocksBuilder builder = new ChildBlocksBuilder(appendUntilEmptyLine( line, source )); + return new ParagraphBlock( builder.getBlocks() ); } /** Index: src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java =================================================================== --- src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java (revision 595967) +++ src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java (working copy) @@ -291,6 +291,28 @@ assertEquals( 2, result.split( "end:sectionTitle2\n" ).length ); } + /** @throws Exception */ + public void testNestedFormats() + throws Exception + { + String result = locateAndParseTestSourceFile( "nested-format" ); + + assertContainsLines( result, "begin:bold\nbegin:italic\ntext: bold italic\nend:italic" ); + assertContainsLines( result, "begin:italic\nbegin:bold\ntext: italic bold\nend:bold" ); + assertContainsLines( result, "begin:bold\nbegin:monospaced\ntext: bold monospaced\nend:monospaced" ); + assertContainsLines( result, "text: A paragraph with \nbegin:bold\ntext: bold \nbegin:italic\ntext: italic\nend:italic" ); + assertContainsLines( result, "begin:italic\ntext: italic \nbegin:bold\ntext: bold\nend:bold" ); + assertContainsLines( result, "begin:bold\ntext: bold \nbegin:monospaced\ntext: monospaced\nend:monospaced" ); + // 2 paragraphs in the input... + assertEquals( 3, result.split( "end:paragraph\n" ).length ); + // 6 bolds in the input... + assertEquals( 7, result.split( "end:bold\n" ).length ); + // 4 italics in the input... + assertEquals( 5, result.split( "end:italic\n" ).length ); + // 2 monospaced in the input... + assertEquals( 3, result.split( "end:monospaced\n" ).length ); + } + private void assertContainsLines( String message, String result, String lines ) { lines = StringUtils.replace( lines, "\n", EOL ); Index: src/test/resources/nested-format.confluence =================================================================== --- src/test/resources/nested-format.confluence (revision 0) +++ src/test/resources/nested-format.confluence (revision 0) @@ -0,0 +1,6 @@ + +A paragraph with *_bold italic_* _*italic bold*_ *{{bold monospaced}}* + +A paragraph with *bold _italic_* _italic *bold*_ *bold {{monospaced}}* + +