Author: vmassol Date: Fri Jan 18 00:40:40 2008 New Revision: 613103 URL: http://svn.apache.org/viewvc?rev=613103&view=rev Log: DOXIA-200: Add a Parser for XWiki
* Macro parsing should now work fine for most cases Modified: maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroBlock.java maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroParser.java maven/sandbox/trunk/doxia/doxia-module-xwiki/src/test/java/org/apache/maven/doxia/module/xwiki/parser/MacroParserTest.java Modified: maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroBlock.java URL: http://svn.apache.org/viewvc/maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroBlock.java?rev=613103&r1=613102&r2=613103&view=diff ============================================================================== --- maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroBlock.java (original) +++ maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroBlock.java Fri Jan 18 00:40:40 2008 @@ -3,9 +3,9 @@ import org.apache.maven.doxia.module.confluence.parser.AbstractFatherBlock; import org.apache.maven.doxia.sink.Sink; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; public class MacroBlock extends AbstractFatherBlock @@ -15,7 +15,7 @@ private Map parameters; private String content; - + public MacroBlock( String name, Map parameters, String content, List childBlocks ) { super( childBlocks ); @@ -41,7 +41,7 @@ public Map getParameters() { - Map newParameters = new HashMap(this.parameters); + Map newParameters = new HashMap( this.parameters ); return newParameters; } Modified: maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroParser.java URL: http://svn.apache.org/viewvc/maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroParser.java?rev=613103&r1=613102&r2=613103&view=diff ============================================================================== --- maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroParser.java (original) +++ maven/sandbox/trunk/doxia/doxia-module-xwiki/src/main/java/org/apache/maven/doxia/module/xwiki/parser/MacroParser.java Fri Jan 18 00:40:40 2008 @@ -11,7 +11,22 @@ * This class was written with performance in mind and thus is lacking clarity. Since this code is * in the rendering path it's important it be very fast. * <p/> - * NOTE: Do NOT use this parser yet as it's not ready and not working. + * TODO: Compare speed with using regex. For example, something like this: + * <pre><code> + * Pattern p = Pattern.compile("(?:(.*)(?::)?)?(.*)?(?:/)}(?:(.*)\\{(?:\\/)\1})?"); + * <p/> + * Matcher m = p.matcher(macro); + * m.find(); + * Map parameters = new HashMap(); + * if (m.group(2) != null && m.group(2).length() > 0) { + * StringTokenizer st = new StringTokenizer(m.group(2), "|"); + * while (st.hasMoreTokens()) { + * String param = st.nextToken(); + * StringTokenizer st2 = new StringTokenizer(param, "="); + * parameters.put(st2.nextToken(), st2.nextToken()); + * } + * } + * </code></pre> */ public class MacroParser { @@ -81,14 +96,17 @@ break; // {macroname:... /}. Contraction of {macroname:...}{/macroname} case '/': - if ( state == STATE_PARAM_VALUE || state == STATE_NAME) + if ( state == STATE_PARAM_VALUE || state == STATE_NAME ) { if ( charAt( input, i ) == '}' ) { i++; - if (state == STATE_PARAM_VALUE) { - parameters.put(parameterName, text.toString()); - } else { + if ( state == STATE_PARAM_VALUE ) + { + parameters.put( parameterName, text.toString() ); + } + else + { macroName = text.toString(); } state = STATE_END; @@ -99,9 +117,12 @@ text.append( c ); } } - else if (state == STATE_CONTENT) { - text.append(c); - } else { + else if ( state == STATE_CONTENT ) + { + text.append( c ); + } + else + { throw new ParseException( "Invalid position for character '/' in Macro" ); } break; @@ -111,32 +132,48 @@ // * {newmacro..}...{/newmacro} // * {oldsinglelinemacro:...} // * {oldmultilinemacro:...}...{oldmultilinemacro} - // * {oldmultilinemacro:...}...{/oldmultilinemacro} - if ( state == STATE_PARAM_VALUE) + if ( state == STATE_PARAM_VALUE ) { - parameters.put(parameterName, text.toString()); + parameters.put( parameterName, text.toString() ); text = new StringBuffer(); // {macro:...} if ( isInCompatibilityMode ) { - // TODO - throw new ParseException( "Compatibility mode for macros not implemented yet" ); + // Since we can't guess if a macro is a multiline one or a single line one we rely on + // a static list of known multiline macros... + if ( multilineMacros.contains( macroName ) ) + { + state = STATE_CONTENT; + } + else + { + state = STATE_END; + } } else { state = STATE_CONTENT; } } - else if ( state == STATE_NAME) { + else if ( state == STATE_NAME ) + { macroName = text.toString(); text = new StringBuffer(); // {macro:...} if ( isInCompatibilityMode ) { - // TODO - throw new ParseException( "Compatibility mode for macros not implemented yet" ); + // Since we can't guess if a macro is a multiline one or a single line one we rely on + // a static list of known multiline macros... + if ( multilineMacros.contains( macroName ) ) + { + state = STATE_CONTENT; + } + else + { + state = STATE_END; + } } else { @@ -172,10 +209,12 @@ content = text.toString(); i++; char cc; - do { + do + { i++; cc = input.charAt( i ); - } while ( cc != '}' && i < input.length() ); + } + while ( cc != '}' && i < input.length() ); state = STATE_END; } else @@ -189,8 +228,23 @@ { if ( isInCompatibilityMode ) { - // TODO - throw new ParseException( "Compatibility mode for macros not implemented yet" ); + // Allow closing macros without using the '/' character + // TODO: Add special support for code macros nested in other code macros and for + // style macros nested in other style macros. + + // Verify that the following characters are the name of the macro and thus that this + // '}' is the beginning of the macro closing. + int pos = input.indexOf( macroName + "}", i + 1 ); + if ( pos == i + 1 ) + { + state = STATE_END; + i += macroName.length() + 1; + content = text.toString(); + } + else + { + text.append( c ); + } } else { @@ -207,7 +261,7 @@ // supported too in param values. if ( state == STATE_PARAM_VALUE ) { - parameters.put(parameterName, text.toString()); + parameters.put( parameterName, text.toString() ); text = new StringBuffer(); state = STATE_PARAM_NAME; } @@ -237,13 +291,27 @@ } break; default: - text.append( c ); + // Any non alphanumeric character found when parsing the macro name should stop the parsing since + // it means the text being parsed is not a macro. + if ( ( state == STATE_NAME ) && ( ( c < 'a' || c > 'z' ) && ( c < 'A' || c > 'Z' ) ) ) + { + // Invalid macro, exit + state = STATE_END; + i = position - 1; + } + else + { + text.append( c ); + } } i++; } - blocks.add( new MacroBlock( macroName, parameters, content, new ArrayList() ) ); + if ( macroName != null ) + { + blocks.add( new MacroBlock( macroName, parameters, content, new ArrayList() ) ); + } return i; } Modified: maven/sandbox/trunk/doxia/doxia-module-xwiki/src/test/java/org/apache/maven/doxia/module/xwiki/parser/MacroParserTest.java URL: http://svn.apache.org/viewvc/maven/sandbox/trunk/doxia/doxia-module-xwiki/src/test/java/org/apache/maven/doxia/module/xwiki/parser/MacroParserTest.java?rev=613103&r1=613102&r2=613103&view=diff ============================================================================== --- maven/sandbox/trunk/doxia/doxia-module-xwiki/src/test/java/org/apache/maven/doxia/module/xwiki/parser/MacroParserTest.java (original) +++ maven/sandbox/trunk/doxia/doxia-module-xwiki/src/test/java/org/apache/maven/doxia/module/xwiki/parser/MacroParserTest.java Fri Jan 18 00:40:40 2008 @@ -5,7 +5,8 @@ import java.util.ArrayList; import java.util.List; -public class MacroParserTest extends TestCase +public class MacroParserTest + extends TestCase { private MacroParser parser; @@ -14,76 +15,125 @@ parser = new MacroParser(); } - public void testParseSimplestMacroWithCondensedClosingStyle() throws Exception + public void testParseSimplestMacroWithCondensedClosingStyle() + throws Exception { List blocks = new ArrayList(); String macro = "{macro/}"; - int pos = parser.parse( macro + " ...", 1, blocks); + int pos = parser.parse( macro + " ...", 1, blocks ); - assertEquals(macro.length(), pos); - assertEquals(1, blocks.size()); - MacroBlock macroBlock = (MacroBlock) blocks.get(0); - assertEquals("macro", macroBlock.getName()); - assertEquals("", macroBlock.getContent()); - assertTrue(macroBlock.getParameters().isEmpty()); + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "macro", macroBlock.getName() ); + assertEquals( "", macroBlock.getContent() ); + assertTrue( macroBlock.getParameters().isEmpty() ); } - public void testParseSimplestMacroWithExpandedClosingStyle() throws Exception + public void testParseSimplestMacroWithExpandedClosingStyle() + throws Exception { List blocks = new ArrayList(); String macro = "{macro}{/macro}"; - int pos = parser.parse( macro + " ...", 1, blocks); + int pos = parser.parse( macro + " ...", 1, blocks ); - assertEquals(macro.length(), pos); - assertEquals(1, blocks.size()); - MacroBlock macroBlock = (MacroBlock) blocks.get(0); - assertEquals("macro", macroBlock.getName()); - assertEquals("", macroBlock.getContent()); - assertTrue(macroBlock.getParameters().isEmpty()); + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "macro", macroBlock.getName() ); + assertEquals( "", macroBlock.getContent() ); + assertTrue( macroBlock.getParameters().isEmpty() ); } - public void testParseMacroWithOneParameter() throws Exception + public void testParseMacroWithOneParameter() + throws Exception { List blocks = new ArrayList(); String macro = "{macro:param1=value1/}"; - int pos = parser.parse( macro + " ...", 1, blocks); + int pos = parser.parse( macro + " ...", 1, blocks ); - assertEquals(macro.length(), pos); - assertEquals(1, blocks.size()); - MacroBlock macroBlock = (MacroBlock) blocks.get(0); - assertEquals("macro", macroBlock.getName()); - assertEquals("", macroBlock.getContent()); - assertEquals(1, macroBlock.getParameters().size()); - assertEquals("value1", macroBlock.getParameters().get("param1")); + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "macro", macroBlock.getName() ); + assertEquals( "", macroBlock.getContent() ); + assertEquals( 1, macroBlock.getParameters().size() ); + assertEquals( "value1", macroBlock.getParameters().get( "param1" ) ); } - public void testParseMacroWithSeveralParameters() throws Exception + public void testParseMacroWithSeveralParameters() + throws Exception { List blocks = new ArrayList(); String macro = "{macro:param1=value1|param2=value2/}"; - int pos = parser.parse( macro + " ...", 1, blocks); + int pos = parser.parse( macro + " ...", 1, blocks ); - assertEquals(macro.length(), pos); - assertEquals(1, blocks.size()); - MacroBlock macroBlock = (MacroBlock) blocks.get(0); - assertEquals("macro", macroBlock.getName()); - assertEquals("", macroBlock.getContent()); - assertEquals(2, macroBlock.getParameters().size()); - assertEquals("value1", macroBlock.getParameters().get("param1")); - assertEquals("value2", macroBlock.getParameters().get("param2")); + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "macro", macroBlock.getName() ); + assertEquals( "", macroBlock.getContent() ); + assertEquals( 2, macroBlock.getParameters().size() ); + assertEquals( "value1", macroBlock.getParameters().get( "param1" ) ); + assertEquals( "value2", macroBlock.getParameters().get( "param2" ) ); } - public void testParseMacroWithContent() throws Exception + public void testParseMacroWithContent() + throws Exception { List blocks = new ArrayList(); String macro = "{macro}Some /=|content{/macro}"; - int pos = parser.parse( macro + " ...", 1, blocks); + int pos = parser.parse( macro + " ...", 1, blocks ); - assertEquals(macro.length(), pos); - assertEquals(1, blocks.size()); - MacroBlock macroBlock = (MacroBlock) blocks.get(0); - assertEquals("macro", macroBlock.getName()); - assertEquals("Some /=|content", macroBlock.getContent()); - assertEquals(0, macroBlock.getParameters().size()); + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "macro", macroBlock.getName() ); + assertEquals( "Some /=|content", macroBlock.getContent() ); + assertEquals( 0, macroBlock.getParameters().size() ); + } + + public void testParseMacroWithInvalidMacroName() + throws Exception + { + List blocks = new ArrayList(); + // This is not a macro. It should be ignored and no macro block should be created + String macro = "{[link]/}"; + int pos = parser.parse( macro + " ...", 1, blocks ); + + assertEquals( 1, pos ); + assertEquals( 0, blocks.size() ); + } + + public void testParseOldStyleMacroInCompatibilityModeWhenMultilineMacro() + throws Exception + { + parser.setCompatibilityMode( true ); + List blocks = new ArrayList(); + String macro = "{code}Some content here{code}"; + int pos = parser.parse( macro + " ...", 1, blocks ); + + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "code", macroBlock.getName() ); + assertEquals( "Some content here", macroBlock.getContent() ); + assertEquals( 0, macroBlock.getParameters().size() ); + } + + public void testParseOldStyleMacroInCompatibilityModeWhenSinglelineMacro() + throws Exception + { + parser.setCompatibilityMode( true ); + List blocks = new ArrayList(); + String macro = "{somesinglelinemacro}"; + int pos = parser.parse( macro + " ...", 1, blocks ); + + assertEquals( macro.length(), pos ); + assertEquals( 1, blocks.size() ); + MacroBlock macroBlock = (MacroBlock) blocks.get( 0 ); + assertEquals( "somesinglelinemacro", macroBlock.getName() ); + assertEquals( "", macroBlock.getContent() ); + assertEquals( 0, macroBlock.getParameters().size() ); } }