Index: /home/herve/projet/workspace/maven-reporting-impl/pom.xml
===================================================================
--- /home/herve/projet/workspace/maven-reporting-impl/pom.xml	(revision 514394)
+++ /home/herve/projet/workspace/maven-reporting-impl/pom.xml	(working copy)
@@ -7,9 +7,9 @@
   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
@@ -81,5 +81,11 @@
       <artifactId>maven-reporting-api</artifactId>
       <version>2.0.4</version>
     </dependency>
+    <dependency>
+      <groupId>junit-addons</groupId>
+      <artifactId>junit-addons</artifactId>
+      <version>1.4</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
Index: /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReport.java
===================================================================
--- /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReport.java	(revision 514240)
+++ /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReport.java	(working copy)
@@ -19,8 +19,6 @@
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.doxia.sink.SinkFactory;
 import org.apache.maven.doxia.siterenderer.Renderer;
-import org.apache.maven.doxia.siterenderer.RendererException;
-import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.project.MavenProject;
@@ -26,7 +24,6 @@
 import org.apache.maven.project.MavenProject;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.Locale;
 
 /**
@@ -43,8 +40,6 @@
 
     private SinkFactory sinkFactory;
 
-    private Locale locale = Locale.ENGLISH;
-
     protected abstract Renderer getSiteRenderer();
 
     protected abstract String getOutputDirectory();
Index: /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReportRenderer.java
===================================================================
--- /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReportRenderer.java	(revision 526177)
+++ /home/herve/projet/workspace/maven-reporting-impl/src/main/java/org/apache/maven/reporting/AbstractMavenReportRenderer.java	(working copy)
@@ -21,10 +21,10 @@
 import org.apache.maven.doxia.sink.Sink;
 import org.codehaus.plexus.util.StringUtils;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.List;
 import java.util.Properties;
 
 /**
@@ -350,7 +350,7 @@
         }
         else
         {
-            Map segments = applyPattern( text );
+            List segments = applyPattern( text );
 
             if ( segments == null )
             {
@@ -358,12 +358,10 @@
             }
             else
             {
-                for ( Iterator it = segments.entrySet().iterator(); it.hasNext(); )
+                for ( Iterator it = segments.iterator(); it.hasNext(); )
                 {
-                    Map.Entry entry = (Map.Entry) it.next();
-
-                    String name = (String) entry.getKey();
-                    String href = (String) entry.getValue();
+                    String name = (String) it.next();
+                    String href = (String) it.next();
 
                     if ( href == null )
                     {
@@ -512,7 +510,7 @@
      * @param text a text with or without the pattern <code>{text, url}</code>
      * @return a map of text/href
      */
-    private static Map applyPattern( String text )
+    private static List applyPattern( String text )
     {
         if ( StringUtils.isEmpty( text ) )
         {
@@ -521,7 +519,7 @@
 
         // Map defined by key/value name/href
         // If href == null, it means
-        Map segments = new LinkedHashMap();
+        List segments = new ArrayList();
 
         // TODO Special case http://jira.codehaus.org/browse/MEV-40
         if ( text.indexOf( "${" ) != -1 )
@@ -530,11 +528,13 @@
             int lastSemi = text.lastIndexOf( "}" );
             if ( lastComma != -1 && lastSemi != -1 )
             {
-                segments.put( text.substring( lastComma + 1, lastSemi ).trim(), null );
+                segments.add( text.substring( lastComma + 1, lastSemi ).trim() );
+                segments.add( null );
             }
             else
             {
-                segments.put( text, null );
+                segments.add( text );
+                segments.add( null );
             }
 
             return segments;
@@ -554,6 +554,9 @@
                 if ( i + 1 < text.length() && text.charAt( i + 1 ) == '\'' )
                 {
                     i++;
+                    segments.add( text.substring( lastOffset, i ) );
+                    segments.add( null );
+                    lastOffset = i + 1;
                 }
                 else
                 {
@@ -571,11 +574,12 @@
                             {
                                 if ( i != 0 ) // handle { at first character
                                 {
-                                    segments.put( text.substring( lastOffset, i ), null );
+                                    segments.add( text.substring( lastOffset, i ) );
+                                    segments.add( null );
                                 }
                                 lastOffset = i + 1;
-                                braceStack++;
                             }
+                            braceStack++;
                         }
                         break;
                     case '}':
@@ -590,12 +594,13 @@
                                 int lastComma = subString.lastIndexOf( "," );
                                 if ( lastComma != -1 )
                                 {
-                                    segments.put( subString.substring( 0, lastComma ).trim(),
-                                                  subString.substring( lastComma + 1 ).trim() );
+                                    segments.add( subString.substring( 0, lastComma ).trim() );
+                                    segments.add( subString.substring( lastComma + 1 ).trim() );
                                 }
                                 else
                                 {
-                                    segments.put( subString.substring( 0, lastComma ).trim(), null );
+                                    segments.add( subString.substring( 0, lastComma ).trim() );
+                                    segments.add( null );
                                 }
                             }
                         }
@@ -611,7 +616,8 @@
 
         if ( !StringUtils.isEmpty( text.substring( lastOffset, text.length() ) ) )
         {
-            segments.put( text.substring( lastOffset, text.length() ), null );
+            segments.add( text.substring( lastOffset, text.length() ) );
+            segments.add( null );
         }
 
         if ( braceStack != 0 )
@@ -619,7 +625,13 @@
             throw new IllegalArgumentException( "Unmatched braces in the pattern." );
         }
 
-        return Collections.unmodifiableMap( segments );
+        if ( inQuote )
+        {
+            //throw new IllegalArgumentException( "Unmatched quote in the pattern." );
+            //TODO: warning...
+        }
+
+        return Collections.unmodifiableList( segments );
     }
 
     public abstract String getTitle();
Index: /home/herve/projet/workspace/maven-reporting-impl/src/test/java/org/apache/maven/reporting/AbstractMavenReportRendererTest.java
===================================================================
--- /home/herve/projet/workspace/maven-reporting-impl/src/test/java/org/apache/maven/reporting/AbstractMavenReportRendererTest.java	(revision 0)
+++ /home/herve/projet/workspace/maven-reporting-impl/src/test/java/org/apache/maven/reporting/AbstractMavenReportRendererTest.java	(revision 0)
@@ -0,0 +1,100 @@
+package org.apache.maven.reporting;
+
+/*
+ * Copyright 2001-2007 The Apache Software Foundation.
+ *
+ * Licensed 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.Iterator;
+import java.util.List;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import junitx.util.PrivateAccessor;
+
+public class AbstractMavenReportRendererTest extends TestCase
+{
+    private static List applyPattern( String pattern ) throws Throwable
+    {
+        return (List) PrivateAccessor.invoke( AbstractMavenReportRenderer.class, "applyPattern",
+                                             new Class[] { String.class }, new Object[] { pattern } );
+    }
+
+    private static void checkPattern( String pattern, String[] expectedResult ) throws Throwable
+    {
+        List result = applyPattern( pattern );
+        Assert.assertEquals( "result size", expectedResult.length, result.size() );
+        int i = 0;
+        for ( Iterator it = result.iterator(); it.hasNext(); )
+        {
+            String name = (String) it.next();
+            String href = (String) it.next();
+            Assert.assertEquals( expectedResult[i], name );
+            Assert.assertEquals( expectedResult[i + 1], href );
+            i += 2;
+        }
+    }
+
+    private static void checkPatternIllegalArgument( String cause, String pattern ) throws Throwable
+    {
+        try
+        {
+            applyPattern( pattern );
+            Assert.fail( cause + " should throw an IllegalArgumentException" );
+        }
+        catch ( IllegalArgumentException iae )
+        {
+            // ok
+        }
+    }
+
+    public void testApplyPattern() throws Throwable
+    {
+        // the most simple test
+        checkPattern( "test {text,url}", new String[] { "test ", null, "text", "url" } );
+
+        // check that link content is trimmed, and no problem if 2 text values are the same
+        checkPattern( "test{ text , url }test", new String[] { "test", null, "text", "url", "test", null } );
+
+        // check brace stacking
+        checkPattern( "test{ {text} , url }", new String[] { "test", null, "{text}", "url" } );
+
+        // check quoting
+        checkPatternIllegalArgument( "unmatched brace", "{" );
+        checkPattern( "'{'", new String[] { "'{'", null } );
+        checkPattern( " ' { '.", new String[] { " ' { '.", null } );
+
+        // unmatched quote: the actual behavior is to ignore that they are unmatched
+        checkPattern( " '", new String[] { " '", null } );
+        // but shouldn't it be different and throw an IllegalArgumentException?
+        //    checkPatternIllegalArgument( "unmatched quote", " ' " );
+        //    checkPatternIllegalArgument( "unmatched quote", " '" );
+        // impact is too important to make the change for the moment
+
+        // check double quoting
+        checkPattern( " ''", new String[] { " '", null } );
+        checkPattern( " '' ", new String[] { " '", null } );
+        checkPattern( " ''   ", new String[] { " '", null } );
+
+        // real world cases with quote
+        checkPattern( "project''s info", new String[] { "project'", null, "s info", null } );
+        checkPattern( "it''s a question of {chance, http://en.wikipedia.org/wiki/Chance}",
+                      new String[] { "it'", null, "s a question of ", null, "chance", "http://en.wikipedia.org/wiki/Chance" } );
+
+        // throwing an IllegalArgumentException in case of unmatched quote would avoid the following:
+        checkPattern( "it's a question of {chance, http://en.wikipedia.org/wiki/Chance}",
+                      new String[] { "it's a question of {chance, http://en.wikipedia.org/wiki/Chance}", null } );
+    }
+
+}

