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 0) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/ChildBlocksBuilder.java (revision 0) @@ -0,0 +1,174 @@ +package org.apache.maven.doxia.module.confluence.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 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. + * + * @author Dave Syer + */ +public class ChildBlocksBuilder +{ + + /** + * 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 ) + { + + 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 ); + + switch ( c ) + { + case '*': + if ( insideBold ) + { + TextBlock tb = new TextBlock( text.toString() ); + blocks.add( new BoldBlock( Arrays.asList( new Block[] { tb } ) ) ); + text = new StringBuffer(); + } + else + { + blocks.add( new TextBlock( text.toString() ) ); + text = new StringBuffer(); + insideBold = true; + } + + break; + case '_': + if ( insideItalic ) + { + TextBlock tb = new TextBlock( text.toString() ); + blocks.add( new ItalicBlock( Arrays.asList( new Block[] { tb } ) ) ); + text = new StringBuffer(); + } + else + { + blocks.add( new TextBlock( text.toString() ) ); + text = new StringBuffer(); + insideItalic = true; + } + + break; + case '[': + insideLink = true; + blocks.add( new TextBlock( text.toString() ) ); + text = new StringBuffer(); + break; + case ']': + if ( insideLink ) + { + String link = text.toString(); + + if ( link.indexOf( "|" ) > 0 ) + { + String[] pieces = StringUtils.split( text.toString(), "|" ); + blocks.add( new LinkBlock( pieces[1], pieces[0] ) ); + } + else + { + blocks.add( new LinkBlock( link, link ) ); + } + + text = new StringBuffer(); + } + + break; + case '{': + + if ( input.charAt( i + 1 ) == '{' ) + { + i++; + blocks.add( new TextBlock( text.toString() ) ); + text = new StringBuffer(); + } + else + { + text.append( c ); + } + + break; + case '}': + + // System.out.println( "line = " + line ); + + if ( input.charAt( i + 1 ) == '}' ) + { + i++; + TextBlock tb = new TextBlock( text.toString() ); + blocks.add( new MonospaceBlock( Arrays.asList( new Block[] { tb } ) ) ); + text = new StringBuffer(); + } + else + { + text.append( c ); + } + + break; + case '\\': + + // System.out.println( "line = " + line ); + + if ( input.charAt( i + 1 ) == '\\' ) + { + i++; + blocks.add( new TextBlock( text.toString() ) ); + text = new StringBuffer(); + blocks.add( new LinebreakBlock() ); + } + else + { + text.append( input.charAt( i + 1 ) ); + } + + break; + default: + text.append( c ); + } + } + + if ( text.length() > 0 ) + { + blocks.add( new TextBlock( text.toString() ) ); + } + + return blocks; + } +} 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 589148) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/list/TreeListBuilder.java (working copy) @@ -19,12 +19,12 @@ * under the License. */ -import org.apache.maven.doxia.module.confluence.parser.TextBlock; - import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Iterator; +import org.apache.maven.doxia.module.confluence.parser.ChildBlocksBuilder; + public class TreeListBuilder { private TreeComponent root; @@ -114,20 +114,20 @@ { TreeComponent child = (TreeComponent) i.next(); - List text = new ArrayList(); + List childBlocks = new ArrayList(); if ( child.getFather() != null ) { - text.add( new TextBlock( child.getText() ) ); + childBlocks.addAll( new ChildBlocksBuilder().getBlocks( child.getText() ) ); } if ( child.getChildren().size() != 0 ) { - blocks.add( new ListItemBlock( text, getList( child ) ) ); + blocks.add( new ListItemBlock( childBlocks, getList( child ) ) ); } else { - blocks.add( new ListItemBlock( text ) ); + blocks.add( new ListItemBlock( childBlocks ) ); } } 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 589148) +++ src/main/java/org/apache/maven/doxia/module/confluence/parser/ParagraphBlockParser.java (working copy) @@ -19,13 +19,8 @@ * under the License. */ -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.apache.maven.doxia.parser.ParseException; import org.apache.maven.doxia.util.ByLineSource; -import org.codehaus.plexus.util.StringUtils; public class ParagraphBlockParser implements BlockParser @@ -39,12 +34,7 @@ public Block visit( String line, ByLineSource source ) throws ParseException { - boolean insideBold = false; - boolean insideItalic = false; - boolean insideLink = false; - List blocks = new ArrayList(); - StringBuffer text = new StringBuffer(); do @@ -55,118 +45,15 @@ break; } - for ( int i = 0; i < line.length(); i++ ) + if ( text.length() == 0 ) { - char c = line.charAt( i ); - - switch ( c ) - { - case '*': - if ( insideBold ) - { - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new BoldBlock( Arrays.asList( new Block[] { tb } ) ) ); - text = new StringBuffer(); - } - else - { - blocks.add( new TextBlock( text.toString() ) ); - text = new StringBuffer(); - insideBold = true; - } - - break; - case '_': - if ( insideItalic ) - { - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new ItalicBlock( Arrays.asList( new Block[] { tb } ) ) ); - text = new StringBuffer(); - } - else - { - blocks.add( new TextBlock( text.toString() ) ); - text = new StringBuffer(); - insideItalic = true; - } - - break; - case '[': - insideLink = true; - blocks.add( new TextBlock( text.toString() ) ); - text = new StringBuffer(); - break; - case ']': - if ( insideLink ) - { - String link = text.toString(); - - if ( link.indexOf( "|" ) > 0 ) - { - String[] pieces = StringUtils.split( text.toString(), "|" ); - blocks.add( new LinkBlock( pieces[1], pieces[0] ) ); - } - else - { - blocks.add( new LinkBlock( link, link ) ); - } - - text = new StringBuffer(); - } - - break; - case '{': - - if ( line.charAt( i + 1 ) == '{' ) - { - i++; - blocks.add( new TextBlock( text.toString() ) ); - text = new StringBuffer(); - } - else - { - text.append( c ); - } - - break; - case '}': - - // System.out.println( "line = " + line ); - - if ( line.charAt( i + 1 ) == '}' ) - { - i++; - TextBlock tb = new TextBlock( text.toString() ); - blocks.add( new MonospaceBlock( Arrays.asList( new Block[] { tb } ) ) ); - text = new StringBuffer(); - } - else - { - text.append( c ); - } - - break; - case '\\': - - // System.out.println( "line = " + line ); - - if ( line.charAt( i + 1 ) == '\\' ) - { - i++; - blocks.add( new TextBlock( text.toString() ) ); - text = new StringBuffer(); - blocks.add( new LinebreakBlock() ); - } - else - { - text.append( line.charAt( i + 1 ) ); - } - - break; - default: - text.append( c ); - } + text.append( line.trim() ); } + else + { + text.append( " " + line.trim() ); + } + } // TODO: instead of just flying along we should probably test new lines // in the other parsers @@ -182,11 +69,9 @@ // by the list parser. while ( ( line = source.getNextLine() ) != null ); - if ( text.length() > 0 ) - { - blocks.add( new TextBlock( text.toString() ) ); - } + ChildBlocksBuilder builder = new ChildBlocksBuilder(); + - return new ParagraphBlock( blocks ); + return new ParagraphBlock( builder.getBlocks( text.toString() ) ); } } Index: src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java =================================================================== --- src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java (revision 589148) +++ src/test/java/org/apache/maven/doxia/module/confluence/ConfluenceParserTest.java (working copy) @@ -19,14 +19,17 @@ * under the License. */ +import java.io.IOException; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import org.apache.maven.doxia.parser.AbstractParserTest; +import org.apache.maven.doxia.parser.ParseException; import org.apache.maven.doxia.parser.Parser; import org.apache.maven.doxia.sink.Sink; import org.apache.maven.doxia.sink.TextSink; +import org.codehaus.plexus.util.StringUtils; /** * Test class for ConfluenceParser. @@ -42,7 +45,6 @@ private Writer writer; - /** {@inheritDoc} */ protected void setUp() throws Exception @@ -57,6 +59,20 @@ } /** {@inheritDoc} */ + protected void tearDown() + throws Exception + { + if ( output != null ) + output.close(); + if ( reader != null ) + reader.close(); + if ( writer != null ) + writer.close(); + + super.tearDown(); + } + + /** {@inheritDoc} */ protected Parser createParser() { return parser; @@ -69,71 +85,97 @@ } /** @throws Exception */ + public void testParagraphWithSimpleFormatting() + throws Exception + { + String result = locateAndParseTestSourceFile( "simple-paragraph" ); + + assertContainsLines( result, "begin:bold\ntext: bold\n" ); + assertContainsLines( result, "begin:italic\ntext: italic\n" ); + assertContainsLines( result, "begin:monospaced\ntext: monospaced\n" ); + assertContainsLines( result, "begin:link, name: http://jira.codehaus.org\ntext: http://jira.codehaus.org" ); + assertContainsLines( result, "begin:link, name: http://jira.codehaus.org\ntext: JIRA\n" ); + // four paragraphs in the input... + assertEquals( 5, result.split( "end:paragraph" ).length ); + } + + /** @throws Exception */ public void testLineBreak() throws Exception { String lineBreak = getLineBreakString(); - try - { - output = new StringWriter(); - reader = getTestReader( "linebreak", outputExtension() ); - writer = getTestWriter( "linebreak", "txt" ); + String result = locateAndParseTestSourceFile( "linebreak" ); - Sink sink = new TextSink( output ); - createParser().parse( reader, sink ); + assertContainsLines( result, "Line\n" + lineBreak ); + assertContainsLines( result, "with 2\n" + lineBreak ); + assertContainsLines( result, "inline\n" + lineBreak ); + } - // write to file - String expected = output.toString(); - writer.write( expected ); - writer.flush(); + /** @throws Exception */ + public void testSectionTitles() + throws Exception + { + String result = locateAndParseTestSourceFile( "section" ); - assertTrue( expected.indexOf( "Line" + EOL + lineBreak ) != -1 ); - assertTrue( expected.indexOf( "with 2" + EOL + lineBreak ) != -1 ); - assertTrue( expected.indexOf( "inline" + EOL + lineBreak ) != -1 ); - } - finally + for ( int i = 1; i <= 5; i++ ) { - output.close(); - reader.close(); - writer.close(); + assertContainsLines( "Could not locate section " + i + " title", result, "sectionTitle" + i + + "\ntext: Section" + i ); } + + assertContainsLines( "Section title has leading space", result, "sectionTitle1\ntext: TitleWithLeadingSpace" ); } - public void testSectionTitles() + /** @throws Exception */ + public void testNestedBulletList() throws Exception { - try - { - output = new StringWriter(); - reader = getTestReader( "section", outputExtension() ); - writer = getTestWriter( "section", "txt" ); + String result = locateAndParseTestSourceFile( "nested-list" ); - Sink sink = new TextSink( output ); - createParser().parse( reader, sink ); + assertContainsLines( "Nested list not found", result, "begin:listItem\ntext: A top level list item\nbegin:list" ); + // two lists in the input... + assertEquals( 3, result.split( "end:list\n" ).length ); + // ...and 4 list items + assertEquals( 5, result.split( "end:listItem\n" ).length ); + } - // write to file - String expected = output.toString(); - writer.write( expected ); - writer.flush(); + /** @throws Exception */ + public void testListWithSimpleFormatting() + throws Exception + { + String result = locateAndParseTestSourceFile( "simple-list" ); + + assertContainsLines( result, "begin:bold\ntext: bold\n" ); + assertContainsLines( result, "begin:italic\ntext: italic\n" ); + assertContainsLines( result, "begin:monospaced\ntext: monospaced\n" ); + assertContainsLines( result, "begin:link, name: http://jira.codehaus.org\ntext: http://jira.codehaus.org\n" ); + assertContainsLines( result, "begin:link, name: http://jira.codehaus.org\ntext: JIRA\n" ); + assertContainsLines( result, "begin:listItem\ntext: Item with no formatting\nend:listItem\n" ); + // one list in the input... + assertEquals( 2, result.split( "end:list\n" ).length ); + // ...and 5 list items + assertEquals( 6, result.split( "end:listItem\n" ).length ); + } - for ( int i = 1; i <= 5; i++ ) - { - assertTrue( "Could not locate section " + i + " title", - expected.indexOf( "sectionTitle" + i + EOL + "text: " + "Section" + i ) != -1 ); - } - - assertTrue( "Section title has leading space", - expected.indexOf( "sectionTitle1" + EOL + "text: " + "TitleWithLeadingSpace" ) != -1 ); + private void assertContainsLines( String message, String result, String lines ) + { + lines = StringUtils.replace( lines, "\n", EOL ); + if ( message != null ) + { + assertTrue( message, result.indexOf( lines ) != -1 ); } - finally + else { - output.close(); - reader.close(); - writer.close(); + assertTrue( result.indexOf( lines ) != -1 ); } } + private void assertContainsLines( String result, String lines ) + { + this.assertContainsLines( null, result, lines ); + } + private String getLineBreakString() { StringWriter sw = new StringWriter(); @@ -143,4 +185,21 @@ return sw.toString(); } + private String locateAndParseTestSourceFile( String stem ) + throws IOException, ParseException + { + output = new StringWriter(); + reader = getTestReader( stem, outputExtension() ); + writer = getTestWriter( stem, "txt" ); + + Sink sink = new TextSink( output ); + createParser().parse( reader, sink ); + + // write to file + String expected = output.toString(); + writer.write( expected ); + writer.flush(); + return expected; + } + } Index: src/test/resources/nested-list.confluence =================================================================== --- src/test/resources/nested-list.confluence (revision 0) +++ src/test/resources/nested-list.confluence (revision 0) @@ -0,0 +1,8 @@ + +A paragraph + +* A top level list item +** A nested list item +** Another nested list item +* Back at the top level + Index: src/test/resources/simple-list.confluence =================================================================== --- src/test/resources/simple-list.confluence (revision 0) +++ src/test/resources/simple-list.confluence (revision 0) @@ -0,0 +1,6 @@ +* Simple paragraph with *bold* and _italic_ text. +* Here is a link to [JIRA|http://jira.codehaus.org] +* Here is a link with no text [http://jira.codehaus.org] +* This is some {{monospaced}} text. +* Item with no formatting + Index: src/test/resources/simple-paragraph.confluence =================================================================== --- src/test/resources/simple-paragraph.confluence (revision 0) +++ src/test/resources/simple-paragraph.confluence (revision 0) @@ -0,0 +1,8 @@ +Simple paragraph with *bold* and _italic_ text. + +Here is a link to [JIRA|http://jira.codehaus.org] + +Here is a link with no text [http://jira.codehaus.org] + +This is some {{monospaced}} text. +