o promoted linkcheck tool and plugin out of the sandbox

http://mail-archives.apache.org/mod_mbox/maven-doxia-dev/200801.mbox/%3c9ae367340801290528k4f72aba6jaed7062b1ae3902d@mail.gmail.com%3e

git-svn-id: https://svn.apache.org/repos/asf/maven/plugins/trunk@618633 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..e6f1ad4
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.plugins</groupId>
+    <artifactId>maven-plugins</artifactId>
+    <version>8</version>
+  </parent>
+
+  <artifactId>maven-linkcheck-plugin</artifactId>
+  <packaging>maven-plugin</packaging>
+  <name>Maven Linkcheck Plugin</name>
+  <version>1.0-SNAPSHOT</version>
+
+  <prerequisites>
+    <maven>2.0.6</maven>
+  </prerequisites>
+
+  <dependencies>
+    <!-- maven -->
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-project</artifactId>
+      <version>2.0.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-settings</artifactId>
+      <version>2.0.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-artifact</artifactId>
+      <version>2.0.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-plugin-api</artifactId>
+      <version>2.0.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.reporting</groupId>
+      <artifactId>maven-reporting-api</artifactId>
+      <version>2.0.4</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.reporting</groupId>
+      <artifactId>maven-reporting-impl</artifactId>
+      <version>2.0.4</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-doxia-tools</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+
+    <!-- doxia -->
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-module-xhtml</artifactId>
+      <version>${doxia.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-sink-api</artifactId>
+      <version>${doxia.version}</version>
+    </dependency>
+
+    <!-- doxia-sitetools -->
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-decoration-model</artifactId>
+      <version>${doxia-sitetools.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-site-renderer</artifactId>
+      <version>${doxia-sitetools.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-container-default</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-component-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <!-- linkcheck -->
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-linkcheck</artifactId>
+      <version>1.0-beta-1-SNAPSHOT</version>
+    </dependency>
+
+    <!-- plexus -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-i18n</artifactId>
+      <version>1.0-beta-7</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-component-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <!-- misc -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+      <version>1.4.6</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.3</version>
+      </plugin>
+    </plugins>
+  </build>
+
+  <properties>
+    <doxia.version>1.0-alpha-10</doxia.version>
+    <doxia-sitetools.version>1.0-alpha-10</doxia-sitetools.version>
+  </properties>
+</project>
diff --git a/src/main/java/org/apache/maven/plugins/linkcheck/CleanMojo.java b/src/main/java/org/apache/maven/plugins/linkcheck/CleanMojo.java
new file mode 100644
index 0000000..24c6fd9
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/linkcheck/CleanMojo.java
@@ -0,0 +1,63 @@
+package org.apache.maven.plugins.linkcheck;
+
+/*
+ * 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.io.File;
+
+import org.apache.maven.plugin.MojoExecutionException;
+
+/**
+ * Clean <code>Linkcheck</code> generated files by the report.
+ *
+ * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
+ * @version $Id$
+ * @since 1.0
+ * @goal clean
+ */
+public class CleanMojo
+    extends LinkcheckReport
+{
+    /** {@inheritDoc} */
+    public void execute()
+        throws MojoExecutionException
+    {
+        File cache = new File( linkcheckCache );
+        if ( cache.isFile() && cache.exists() )
+        {
+            cache.delete();
+
+            if ( getLog().isDebugEnabled() )
+            {
+                getLog().debug( "The file '" + cache.getAbsolutePath() + "' has been deleted." );
+            }
+        }
+
+        File output = new File( linkcheckOutput );
+        if ( output.isFile() && output.exists() )
+        {
+            output.delete();
+
+            if ( getLog().isDebugEnabled() )
+            {
+                getLog().debug( "The file '" + output.getAbsolutePath() + "' has been deleted." );
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/linkcheck/LinkcheckReport.java b/src/main/java/org/apache/maven/plugins/linkcheck/LinkcheckReport.java
new file mode 100644
index 0000000..b2e3d8f
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/linkcheck/LinkcheckReport.java
@@ -0,0 +1,923 @@
+package org.apache.maven.plugins.linkcheck;
+
+/*
+ * 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.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.doxia.linkcheck.DefaultLinkCheck;
+import org.apache.maven.doxia.linkcheck.HttpBean;
+import org.apache.maven.doxia.linkcheck.LinkCheck;
+import org.apache.maven.doxia.linkcheck.model.LinkcheckFile;
+import org.apache.maven.doxia.linkcheck.model.LinkcheckFileResult;
+import org.apache.maven.doxia.linkcheck.model.LinkcheckModel;
+import org.apache.maven.doxia.module.xhtml.decoration.render.RenderingContext;
+import org.apache.maven.doxia.site.decoration.Body;
+import org.apache.maven.doxia.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.LinkItem;
+import org.apache.maven.doxia.siterenderer.Renderer;
+import org.apache.maven.doxia.siterenderer.RendererException;
+import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.tools.SiteTool;
+import org.apache.maven.doxia.tools.SiteToolException;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.reporting.AbstractMavenReport;
+import org.apache.maven.reporting.MavenReportException;
+import org.apache.maven.settings.Proxy;
+import org.apache.maven.settings.Settings;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Generates a <code>Linkcheck</code> report.
+ *
+ * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
+ * @version $Id$
+ * @since 1.0
+ * @goal linkcheck
+ */
+public class LinkcheckReport
+    extends AbstractMavenReport
+{
+    // ----------------------------------------------------------------------
+    // Report Components
+    // ----------------------------------------------------------------------
+
+    /**
+     * Internationalization.
+     *
+     * @component
+     */
+    protected I18N i18n;
+
+    /**
+     * Doxia Site Renderer.
+     *
+     * @component
+     */
+    protected Renderer siteRenderer;
+
+    /**
+     * SiteTool component.
+     *
+     * @component
+     */
+    protected SiteTool siteTool;
+
+    /**
+     * SiteTool component.
+     *
+     * @component
+     */
+    protected LinkCheck linkCheck;
+
+    // ----------------------------------------------------------------------
+    // Report Parameters
+    // ----------------------------------------------------------------------
+
+    /**
+     * The Maven Project.
+     *
+     * @parameter expression="${project}"
+     * @required
+     * @readonly
+     */
+    protected MavenProject project;
+
+    /**
+     * The reactor projects.
+     *
+     * @parameter expression="${reactorProjects}"
+     * @required
+     * @readonly
+     */
+    protected List reactorProjects;
+
+    /**
+     * Local Repository.
+     *
+     * @parameter expression="${localRepository}"
+     * @required
+     * @readonly
+     */
+    protected ArtifactRepository localRepository;
+
+    /**
+     * Remote repositories used for the project.
+     *
+     * @parameter expression="${project.remoteArtifactRepositories}"
+     * @required
+     */
+    protected List repositories;
+
+    /**
+     * Report output directory.
+     *
+     * @parameter expression="${project.reporting.outputDirectory}"
+     * @required
+     */
+    protected File outputDirectory;
+
+    /**
+     * The Maven Settings.
+     *
+     * @parameter default-value="${settings}"
+     * @required
+     * @readonly
+     */
+    protected Settings settings;
+
+    /**
+     * Directory containing the <code>site.xml</code> file.
+     *
+     * @parameter expression="${basedir}/src/site"
+     * @required
+     */
+    protected File siteDirectory;
+
+    // ----------------------------------------------------------------------
+    // Linkcheck parameters
+    // ----------------------------------------------------------------------
+
+    /**
+     * The settings offline paramater.
+     *
+     * @parameter expression="${settings.offline}"
+     * @required
+     * @readonly
+     */
+    private boolean offline;
+
+    /**
+     * If online, the HTTP method should automatically follow HTTP redirects,
+     * <tt>false</tt> otherwise.
+     *
+     * @parameter default-value="false"
+     */
+    protected boolean httpFollowRedirect;
+
+    /**
+     * The location of the linkcheck cache.
+     *
+     * @parameter expression="${project.build.directory}/linkcheck/linkcheck.cache"
+     * @required
+     */
+    protected String linkcheckCache;
+
+    /**
+     * The location of the linkcheck report.
+     *
+     * @parameter expression="${project.build.directory}/linkcheck/linkcheck.xml"
+     * @required
+     */
+    protected String linkcheckOutput;
+
+    /**
+     * The HTTP method to use. Currently supported are "GET" and "HEAD".
+     * <dl>
+     * <dt>HTTP GET</dt>
+     * <dd>
+     * The HTTP GET method is defined in section 9.3 of
+     * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>:
+     * The GET method means retrieve whatever information (in the form of an
+     * entity) is identified by the Request-URI.
+     * </dd>
+     * <dt>HTTP HEAD</dt>
+     * <dd>
+     * The HTTP HEAD method is defined in section 9.4 of
+     * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>:
+     * The HEAD method is identical to GET except that the server MUST NOT
+     * return a message-body in the response.
+     * </dd>
+     * </dl>
+     *
+     * @parameter default-value="head"
+     */
+    protected String httpMethod;
+
+    /**
+     * The list of HTTP errors to ignored.
+     *
+     * @parameter
+     * @see {@link HttpStatus} for all defined values.
+     */
+    protected int[] excludedHttpStatusErrors;
+
+    /**
+     * The list of HTTP warnings to ignored.
+     *
+     * @parameter
+     * @see {@link HttpStatus} for all defined values.
+     */
+    protected int[] excludedHttpStatusWarnings;
+
+    /**
+     * The list of pages to exclude.
+     *
+     * @parameter
+     */
+    protected String[] excludedPages;
+
+    /**
+     * The list of links to exclude.
+     *
+     * @parameter
+     */
+    protected String[] excludedLinks;
+
+    // ----------------------------------------------------------------------
+    // Public methods
+    // ----------------------------------------------------------------------
+
+    /** {@inheritDoc} */
+    public String getDescription( Locale locale )
+    {
+        return i18n.getString( "linkcheck-report", locale, "report.linkcheck.description" );
+    }
+
+    /** {@inheritDoc} */
+    public String getName( Locale locale )
+    {
+        return i18n.getString( "linkcheck-report", locale, "report.linkcheck.name" );
+    }
+
+    /** {@inheritDoc} */
+    public String getOutputName()
+    {
+        return "linkcheck";
+    }
+
+    /** {@inheritDoc} */
+    public boolean canGenerateReport()
+    {
+        boolean can = outputDirectory.exists();
+
+        if ( !can )
+        {
+            if ( getLog().isWarnEnabled() )
+            {
+                getLog().warn( "Linkcheck report skipped. You need to call 'mvn site' before." );
+            }
+        }
+
+        return can;
+    }
+
+    /** {@inheritDoc} */
+    public void execute()
+        throws MojoExecutionException
+    {
+        if ( !canGenerateReport() )
+        {
+            return;
+        }
+
+        try
+        {
+            DecorationModel model = new DecorationModel();
+            model.setBody( new Body() );
+            Map attributes = new HashMap();
+            attributes.put( "outputEncoding", "UTF-8" );
+            attributes.put( "project", project );
+            Locale locale = Locale.getDefault();
+            Artifact skinArtifact = siteTool.getDefaultSkinArtifact( localRepository, repositories );
+            SiteRenderingContext siteContext = siteRenderer.createContextForSkin( skinArtifact.getFile(), attributes,
+                                                                                  model, getName( locale ), locale );
+
+            RenderingContext context = new RenderingContext( outputDirectory, getOutputName() + ".html" );
+
+            SiteRendererSink sink = new SiteRendererSink( context );
+            generate( sink, locale );
+
+            outputDirectory.mkdirs();
+
+            Writer writer = new FileWriter( new File( outputDirectory, getOutputName() + ".html" ) );
+
+            siteRenderer.generateDocument( writer, sink, siteContext );
+
+            siteRenderer.copyResources( siteContext, new File( project.getBasedir(), "src/site/resources" ),
+                                        outputDirectory );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
+                + " report generation.", e );
+        }
+        catch ( SiteToolException e )
+        {
+            throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
+                + " report generation.", e );
+        }
+        catch ( RendererException e )
+        {
+            throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
+                + " report generation.", e );
+        }
+        catch ( MavenReportException e )
+        {
+            throw new MojoExecutionException( "An error has occurred in " + getName( Locale.ENGLISH )
+                + " report generation.", e );
+        }
+    }
+
+    // ----------------------------------------------------------------------
+    // Protected methods
+    // ----------------------------------------------------------------------
+
+    /** {@inheritDoc} */
+    protected String getOutputDirectory()
+    {
+        return outputDirectory.getAbsolutePath();
+    }
+
+    /** {@inheritDoc} */
+    protected MavenProject getProject()
+    {
+        return project;
+    }
+
+    /** {@inheritDoc} */
+    protected Renderer getSiteRenderer()
+    {
+        return siteRenderer;
+    }
+
+    /** {@inheritDoc} */
+    protected void executeReport( Locale locale )
+        throws MavenReportException
+    {
+        try
+        {
+            LinkcheckModel result = executeLinkCheck( locale );
+
+            generateReport( locale, result );
+        }
+        catch ( Exception e )
+        {
+            throw new MavenReportException( "IOException: " + e.getMessage(), e );
+        }
+    }
+
+    // ----------------------------------------------------------------------
+    // Private methods
+    // ----------------------------------------------------------------------
+
+    /**
+     * Execute the <code>Linkcheck</code> tool.
+     */
+    private LinkcheckModel executeLinkCheck( Locale locale )
+        throws Exception
+    {
+        // Wrap linkcheck
+        LinkCheck lc = new DefaultLinkCheck();
+        lc.setOnline( !offline );
+        lc.setBasedir( outputDirectory );
+        lc.setReportOutput( new File( linkcheckOutput ) );
+        lc.setLinkCheckCache( new File( linkcheckCache ) );
+        lc.setExcludedLinks( getExcludedLinks( locale ) );
+        lc.setExcludedPages( getExcludedPages() );
+        lc.setExcludedHttpStatusErrors( excludedHttpStatusErrors );
+        lc.setExcludedHttpStatusWarnings( excludedHttpStatusWarnings );
+
+        HttpBean bean = new HttpBean();
+        bean.setMethod( httpMethod );
+        bean.setFollowRedirects( httpFollowRedirect );
+
+        Proxy proxy = settings.getActiveProxy();
+        if ( proxy != null )
+        {
+            bean.setProxyHost( proxy.getHost() );
+            bean.setProxyPort( proxy.getPort() );
+            bean.setProxyUser( proxy.getUsername() );
+            bean.setProxyPassword( proxy.getPassword() );
+        }
+        lc.setHttp( bean );
+
+        return lc.execute();
+    }
+
+    private String[] getExcludedLinks( Locale locale )
+        throws Exception
+    {
+        List linksToExclude = ( excludedLinks != null ? new ArrayList( Arrays.asList( excludedLinks ) ) : new ArrayList() );
+
+        if ( project.getUrl() != null )
+        {
+            // Using interpolated references in the decoration model
+            DecorationModel site = siteTool.getDecorationModel( project, reactorProjects, localRepository,
+                                                                repositories, siteDirectory, locale, "ISO-8859-1",
+                                                                "ISO-8859-1" );
+
+            String baseUrl = project.getUrl();
+            if ( site.getBannerLeft() != null && StringUtils.isNotEmpty( site.getBannerLeft().getHref() ) )
+            {
+                linksToExclude.add( siteTool.getRelativePath( site.getBannerLeft().getHref(), baseUrl ) );
+            }
+            if ( site.getBannerRight() != null && StringUtils.isNotEmpty( site.getBannerRight().getHref() ) )
+            {
+                linksToExclude.add( siteTool.getRelativePath( site.getBannerRight().getHref(), baseUrl ) );
+            }
+            if ( site.getBody() != null && site.getBody().getLinks() != null )
+            {
+                for ( Iterator it = site.getBody().getLinks().iterator(); it.hasNext(); )
+                {
+                    LinkItem link = (LinkItem) it.next();
+                    linksToExclude.add( siteTool.getRelativePath( link.getHref(), baseUrl ) );
+                }
+            }
+        }
+
+        return (String[]) linksToExclude.toArray( new String[0] );
+    }
+
+    private String[] getExcludedPages()
+    {
+        List linksToExclude;
+
+        if ( excludedPages != null )
+        {
+            linksToExclude = Arrays.asList( excludedPages );
+        }
+        else
+        {
+            linksToExclude = new ArrayList();
+        }
+
+        // Exclude this report
+        linksToExclude.add( getOutputName() + ".html" );
+
+        return (String[]) linksToExclude.toArray( new String[0] );
+    }
+
+    /**
+     * Generate the Linkcheck report.
+     *
+     * @param locale the wanted locale
+     * @param linkcheckModel the result of the analysis
+     */
+    private void generateReport( Locale locale, LinkcheckModel linkcheckModel )
+    {
+        getSink().head();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.name" ) );
+        getSink().head_();
+
+        getSink().body();
+
+        if ( linkcheckModel == null )
+        {
+            getSink().section1();
+            getSink().sectionTitle1();
+            getSink().text( getName( locale ) );
+            getSink().sectionTitle1_();
+
+            getSink().paragraph();
+            getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.empty" ) );
+            getSink().paragraph_();
+
+            getSink().section1_();
+
+            getSink().body_();
+            getSink().flush();
+            getSink().close();
+
+            return;
+        }
+
+        // Overview
+        getSink().section1();
+        getSink().sectionTitle1();
+        getSink().text( getName( locale ) );
+        getSink().sectionTitle1_();
+
+        getSink().paragraph();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.overview" ) );
+        getSink().paragraph_();
+
+        getSink().section1_();
+
+        //Statistics
+        generateSummarySection( locale, linkcheckModel );
+
+        //Details
+        generateDetailsSection( locale, linkcheckModel );
+
+        getSink().body_();
+        getSink().flush();
+        getSink().close();
+    }
+
+    private void generateSummarySection( Locale locale, LinkcheckModel linkcheckModel )
+    {
+        // Calculus
+        List linkcheckFiles = linkcheckModel.getFiles();
+
+        int totalFiles = linkcheckFiles.size();
+
+        int totalLinks = 0;
+        int totalValidLinks = 0;
+        int totalErrorLinks = 0;
+        int totalWarningLinks = 0;
+        for ( Iterator it = linkcheckFiles.iterator(); it.hasNext(); )
+        {
+            LinkcheckFile linkcheckFile = (LinkcheckFile) it.next();
+
+            totalLinks += linkcheckFile.getNumberOfLinks();
+            totalValidLinks += linkcheckFile.getNumberOfLinks( LinkcheckFileResult.VALID_LEVEL );
+            totalErrorLinks += linkcheckFile.getNumberOfLinks( LinkcheckFileResult.ERROR_LEVEL );
+            totalWarningLinks += linkcheckFile.getNumberOfLinks( LinkcheckFileResult.WARNING_LEVEL );
+        }
+
+        getSink().section1();
+        getSink().sectionTitle1();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary" ) );
+        getSink().sectionTitle1_();
+
+        // Summary of the analysis parameters
+        getSink().paragraph();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.overview1" ) );
+        getSink().paragraph_();
+
+        getSink().table();
+
+        getSink().tableRow();
+        getSink().tableHeaderCell();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.parameter" ) );
+        getSink().tableHeaderCell_();
+        getSink().tableHeaderCell();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.value" ) );
+        getSink().tableHeaderCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.httpFollowRedirect" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        getSink().text( String.valueOf( httpFollowRedirect ) );
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.httpMethod" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        if ( StringUtils.isEmpty( httpMethod ) )
+        {
+            getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.none" ) );
+        }
+        else
+        {
+            getSink().text( httpMethod );
+        }
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.excludedPages" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        if ( excludedPages == null || excludedPages.length == 0 )
+        {
+            getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.none" ) );
+        }
+        else
+        {
+            getSink().text( StringUtils.join( excludedPages, "," ) );
+        }
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.excludedLinks" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        if ( excludedLinks == null || excludedLinks.length == 0 )
+        {
+            getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.none" ) );
+        }
+        else
+        {
+            getSink().text( StringUtils.join( excludedLinks, "," ) );
+        }
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.excludedHttpStatusErrors" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        if ( excludedHttpStatusErrors == null || excludedHttpStatusErrors.length == 0 )
+        {
+            getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.none" ) );
+        }
+        else
+        {
+            getSink().text( toString( excludedHttpStatusErrors ) );
+        }
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableCell();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.excludedHttpStatusWarnings" ) );
+        getSink().tableCell_();
+        getSink().tableCell();
+        if ( excludedHttpStatusWarnings == null || excludedHttpStatusWarnings.length == 0 )
+        {
+            getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.none" ) );
+        }
+        else
+        {
+            getSink().text( toString( excludedHttpStatusWarnings ) );
+        }
+        getSink().tableCell_();
+        getSink().tableRow_();
+
+        getSink().table_();
+
+        // Summary of the checked files
+        getSink().paragraph();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.overview2" ) );
+        getSink().paragraph_();
+
+        getSink().table();
+
+        //Header
+        generateTableHeader( locale, false );
+
+        //Content
+        getSink().tableRow();
+
+        getSink().tableCell();
+        getSink().bold();
+        getSink().text( totalFiles + "" );
+        getSink().bold_();
+        getSink().tableCell_();
+        getSink().tableCell();
+        getSink().bold();
+        getSink().text( totalLinks + "" );
+        getSink().bold_();
+        getSink().tableCell_();
+        getSink().tableCell();
+        getSink().bold();
+        getSink().text( String.valueOf( totalValidLinks ) );
+        getSink().bold_();
+        getSink().tableCell_();
+        getSink().tableCell();
+        getSink().bold();
+        getSink().text( String.valueOf( totalWarningLinks ) );
+        getSink().bold_();
+        getSink().tableCell_();
+        getSink().tableCell();
+        getSink().bold();
+        getSink().text( String.valueOf( totalErrorLinks ) );
+        getSink().bold_();
+        getSink().tableCell_();
+
+        getSink().tableRow_();
+
+        getSink().table_();
+
+        getSink().section1_();
+    }
+
+    private void generateDetailsSection( Locale locale, LinkcheckModel linkcheckModel )
+    {
+        getSink().section1();
+        getSink().sectionTitle1();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.detail" ) );
+        getSink().sectionTitle1_();
+
+        getSink().paragraph();
+        getSink().rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.detail.overview" ) );
+        getSink().paragraph_();
+
+        getSink().table();
+
+        //Header
+        generateTableHeader( locale, true );
+
+        // Content
+        List linkcheckFiles = linkcheckModel.getFiles();
+        for ( Iterator it = linkcheckFiles.iterator(); it.hasNext(); )
+        {
+            LinkcheckFile linkcheckFile = (LinkcheckFile) it.next();
+
+            getSink().tableRow();
+
+            getSink().tableCell();
+            if ( linkcheckFile.getUnsuccessful() == 0 )
+            {
+                iconValid( locale );
+            }
+            else
+            {
+                iconError( locale );
+            }
+            getSink().tableCell_();
+
+            //            tableCell( createLinkPatternedText( linkcheckFile.getRelativePath(), "./"
+            //                + linkcheckFile.getRelativePath() ) );
+            getSink().tableCell();
+            getSink().link( linkcheckFile.getRelativePath() );
+            getSink().text( linkcheckFile.getRelativePath() );
+            getSink().link_();
+            getSink().tableCell_();
+            getSink().tableCell();
+            getSink().text( String.valueOf( linkcheckFile.getNumberOfLinks() ) );
+            getSink().tableCell_();
+            getSink().tableCell();
+            getSink().text( String.valueOf( linkcheckFile.getNumberOfLinks( LinkcheckFileResult.VALID_LEVEL ) ) );
+            getSink().tableCell_();
+            getSink().tableCell();
+            getSink().text( String.valueOf( linkcheckFile.getNumberOfLinks( LinkcheckFileResult.WARNING_LEVEL ) ) );
+            getSink().tableCell_();
+            getSink().tableCell();
+            getSink().text( String.valueOf( linkcheckFile.getNumberOfLinks( LinkcheckFileResult.ERROR_LEVEL ) ) );
+            getSink().tableCell_();
+
+            getSink().tableRow_();
+
+            // Detail error
+            if ( linkcheckFile.getUnsuccessful() != 0 )
+            {
+                getSink().tableRow();
+
+                getSink().tableCell();
+                getSink().text( "" );
+                getSink().tableCell_();
+
+                // TODO it is due to DOXIA-78
+                getSink().rawText( "<td colspan=\"5\">" );
+
+                getSink().table();
+
+                for ( Iterator it2 = linkcheckFile.getResults().iterator(); it2.hasNext(); )
+                {
+                    LinkcheckFileResult linkcheckFileResult = (LinkcheckFileResult) it2.next();
+
+                    if ( linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.VALID_LEVEL )
+                    {
+                        continue;
+                    }
+
+                    getSink().tableRow();
+
+                    getSink().tableCell();
+                    if ( linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.WARNING_LEVEL )
+                    {
+                        iconWarning( locale );
+                    }
+                    else if ( linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.ERROR_LEVEL )
+                    {
+                        iconError( locale );
+                    }
+                    getSink().tableCell_();
+
+                    getSink().tableCell();
+                    getSink().italic();
+                    getSink().link( linkcheckFileResult.getTarget() );
+                    getSink().text( linkcheckFileResult.getTarget() );
+                    getSink().link_();
+                    getSink().text( ": " );
+                    getSink().text( linkcheckFileResult.getErrorMessage() );
+                    getSink().italic_();
+                    getSink().tableCell_();
+
+                    getSink().tableRow_();
+                }
+
+                getSink().table_();
+
+                getSink().tableCell_();
+
+                getSink().tableRow_();
+            }
+        }
+
+        getSink().tableRow();
+
+        getSink().table_();
+
+        getSink().section1_();
+    }
+
+    private void generateTableHeader( Locale locale, boolean detail )
+    {
+        getSink().tableRow();
+        if ( detail )
+        {
+            getSink().rawText( "<th rowspan=\"2\">" );
+            getSink().text( "" );
+            getSink().tableHeaderCell_();
+        }
+        getSink().rawText( "<th rowspan=\"2\">" );
+        getSink().text( detail ? i18n.getString( "linkcheck-report", locale, "report.linkcheck.detail.table.documents" ) : i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.summary.documents" ) );
+        getSink().tableHeaderCell_();
+        // TODO it is due to DOXIA-78
+        getSink().rawText( "<th colspan=\"4\" center>" );
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.links" ) );
+        getSink().tableHeaderCell_();
+        getSink().rawText( "</th>" );
+        getSink().tableRow_();
+
+        getSink().tableRow();
+        getSink().tableHeaderCell();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.totalLinks" ) );
+        getSink().tableHeaderCell_();
+        getSink().tableHeaderCell();
+        iconValid( locale );
+        getSink().tableHeaderCell_();
+        getSink().tableHeaderCell();
+        iconWarning( locale );
+        getSink().tableHeaderCell_();
+        getSink().tableHeaderCell();
+        iconError( locale );
+        getSink().tableHeaderCell_();
+        getSink().tableRow_();
+    }
+
+    private void iconError( Locale locale )
+    {
+        getSink().figure();
+        getSink().figureCaption();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.error" ) );
+        getSink().figureCaption_();
+        getSink().figureGraphics( "images/icon_error_sml.gif" );
+        getSink().figure_();
+    }
+
+    private void iconValid( Locale locale )
+    {
+        getSink().figure();
+        getSink().figureCaption();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.valid" ) );
+        getSink().figureCaption_();
+        getSink().figureGraphics( "images/icon_success_sml.gif" );
+        getSink().figure_();
+    }
+
+    private void iconWarning( Locale locale )
+    {
+        getSink().figure();
+        getSink().figureCaption();
+        getSink().text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.warning" ) );
+        getSink().figureCaption_();
+        getSink().figureGraphics( "images/icon_warning_sml.gif" );
+        getSink().figure_();
+    }
+
+    private String toString( int[] a )
+    {
+        if ( a == null || a.length == 0 )
+        {
+            return "";
+        }
+
+        StringBuffer buf = new StringBuffer();
+        buf.append( a[0] );
+
+        for ( int i = 1; i < a.length; i++ )
+        {
+            buf.append( ", " );
+            buf.append( a[i] );
+        }
+
+        return buf.toString();
+    }
+}
diff --git a/src/main/resources/linkcheck-report.properties b/src/main/resources/linkcheck-report.properties
new file mode 100644
index 0000000..4b43097
--- /dev/null
+++ b/src/main/resources/linkcheck-report.properties
@@ -0,0 +1,46 @@
+# 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.
+
+report.linkcheck.name=Linkcheck report
+report.linkcheck.description=This is a link to the link check report.
+report.linkcheck.title=Linkcheck report
+report.linkcheck.empty=The linkcheck tool has not generated report.
+report.linkcheck.overview=The Link Check tool will search the generated site, testing each link it finds. The report presents the status of each link tested.
+report.linkcheck.summary=Summary
+report.linkcheck.summary.overview1=The following table presents the summary of the analysis parameters.
+report.linkcheck.summary.overview2=The following table presents the summary of all checked documents.
+report.linkcheck.table.summary.parameter=Parameter
+report.linkcheck.table.summary.value=Value
+report.linkcheck.table.summary.httpFollowRedirect=HTTP redirects activated
+report.linkcheck.table.summary.httpMethod=HTTP method used
+report.linkcheck.table.summary.excludedPages=Pages ignored
+report.linkcheck.table.summary.excludedLinks=Links ignored
+report.linkcheck.table.summary.excludedHttpStatusErrors=HTTP errors ignored
+report.linkcheck.table.summary.excludedHttpStatusWarnings=HTTP warning ignored
+report.linkcheck.table.summary.none=None
+report.linkcheck.table.summary.documents=Number Of Documents Analyzed
+report.linkcheck.table.links=Links
+report.linkcheck.table.totalLinks=Total
+report.linkcheck.table.validLinks=Valid
+report.linkcheck.table.errorLinks=Error
+report.linkcheck.table.warningLinks=Warning
+report.linkcheck.detail=Details
+report.linkcheck.detail.overview=The following table presents the detail of all checked documents.
+report.linkcheck.detail.table.documents=Documents Analyzed
+report.linkcheck.icon.error=error
+report.linkcheck.icon.valid=valid
+report.linkcheck.icon.warning=warning
diff --git a/src/site/apt/index.apt b/src/site/apt/index.apt
new file mode 100644
index 0000000..ee3afcb
--- /dev/null
+++ b/src/site/apt/index.apt
@@ -0,0 +1,44 @@
+ ------
+ Maven Linkcheck Plugin
+ ------
+ Vincent Siveton
+ ------
+ January 2008
+ ------
+
+~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/guides/mini/guide-apt-format.html
+
+Overview
+
+  This plug-in allows you to generate a linkcheck report of your project's documentation.
+
+* Goals Overview
+
+  The Linkcheck Plugin only has two goals.
+
+  * {{{./linkcheck-mojo.html}linkcheck:linkcheck}} Generates a Linkcheck report.
+
+  * {{{./clean-mojo.html}linkcheck:clean}} Delete Linkcheck files.
+
+* Usage
+
+  Instructions on the basic usage of the Linkcheck Plugin can be found
+  {{{./usage.html}here}}.
diff --git a/src/site/apt/limitations.apt b/src/site/apt/limitations.apt
new file mode 100644
index 0000000..b14473d
--- /dev/null
+++ b/src/site/apt/limitations.apt
@@ -0,0 +1,37 @@
+ ------
+ Known Bugs and Limitations
+ ------
+ Vincent Siveton
+ ------
+ 24 September 2007
+ ------
+
+~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/guides/mini/guide-apt-format.html
+
+Known Bugs and Limitations
+
+  Just a brief selection...
+
+* Current limitations
+
+  * The Linkcheck report should run in the post-site phase.
+
+  * Running the Linkcheck plugin automatically during a build doesn't work.
\ No newline at end of file
diff --git a/src/site/apt/usage.apt b/src/site/apt/usage.apt
new file mode 100644
index 0000000..41a383a
--- /dev/null
+++ b/src/site/apt/usage.apt
@@ -0,0 +1,93 @@
+ ------
+ Usage
+ ------
+ Vincent Siveton
+ ------
+ 24 September 2007
+ ------
+
+~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/guides/mini/guide-apt-format.html
+
+Using the Linkcheck plugin
+
+  The Maven Linkcheck Plugin allows you generate a link check report of your documentation.
+
+* Using the Linkcheck plugin from the command-line
+
+  The Linkcheck plugin can be called to execute from the command-line without any
+  additional configurations. Like the other plugins, to run the Linkcheck plugin,
+  you use:
+
++-----
+<project>
+  [...]
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-linkcheck-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </reporting>
+  [...]
+</project>
++-----
+
+  The Linkcheck plugin needs to call the <<<site>>> phase twice:
+
++-----
+  mvn site (to generate the site phase, i.e. the documentation)
+  mvn site (to analyze the result of the generated documentation)
++-----
+
+ Here is a simple report:
+
+[./images/sample.png] Sample report
+
+~~
+~~ * Running the Linkcheck plugin automatically during a build
+~~
+~~   The Linkcheck plugin can be put into a project's pom.xml so that it gets executed
+~~   everytime the project is built.  Below is a sample pom.xml for running the
+~~   Linkcheck plugin in the <<<post-site>>> phase everytime the project is built:
+~~
+~~ +-----
+~~ <project>
+~~   [...]
+~~   <build>
+~~     <plugins>
+~~       <plugin>
+~~         <artifactId>maven-inkcheck-plugin</artifactId>
+~~         <executions>
+~~           <execution>
+~~             <id>site-linkcheck</id>
+~~             <phase>post-site</phase>
+~~             <goals>
+~~               <goal>linkcheck</goal>
+~~             </goals>
+~~           </execution>
+~~         </executions>
+~~       </plugin>
+~~     </plugins>
+~~   </build>
+~~   [...]
+~~ </project>
+~~ +-----
\ No newline at end of file
diff --git a/src/site/resources/images/sample.png b/src/site/resources/images/sample.png
new file mode 100644
index 0000000..5d1cfba
--- /dev/null
+++ b/src/site/resources/images/sample.png
Binary files differ
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..8c62304
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+/*
+ * 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.
+ */
+ -->
+
+<project name="Maven Linkcheck plugin">
+
+  <body>
+
+    <menu name="Overview">
+      <item name="Introduction" href="index.html"/>
+      <item name="Goals" href="linkcheck-mojo.html"/>
+      <item name="Usage" href="usage.html"/>
+      <item name="Limitations" href="limitations.html"/>
+    </menu>
+
+  </body>
+</project>