001package org.apache.maven.doxia.macro.toc;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.StringReader;
023
024import org.apache.maven.doxia.index.IndexEntry;
025import org.apache.maven.doxia.index.IndexingSink;
026import org.apache.maven.doxia.macro.AbstractMacro;
027import org.apache.maven.doxia.macro.Macro;
028import org.apache.maven.doxia.macro.MacroExecutionException;
029import org.apache.maven.doxia.macro.MacroRequest;
030import org.apache.maven.doxia.util.HtmlTools;
031import org.apache.maven.doxia.parser.ParseException;
032import org.apache.maven.doxia.parser.Parser;
033import org.apache.maven.doxia.sink.Sink;
034
035import org.codehaus.plexus.component.annotations.Component;
036import org.codehaus.plexus.util.StringUtils;
037
038/**
039 * Macro to display a <code>Table Of Content</code> in a given <code>Sink</code>.
040 * The input parameters for this macro are:
041 * <dl>
042 * <dt>section</dt>
043 * <dd>Display a TOC for the specified section only, or all sections if 0.<br/>
044 * Positive int, not mandatory, 0 by default.</dd>
045 * <dt>fromDepth</dt>
046 * <dd>Minimal depth of entries to display in the TOC.
047 * Sections are depth 1, sub-sections depth 2, etc.<br/>
048 * Positive int, not mandatory, 0 by default.</dd>
049 * <dt>toDepth</dt>
050 * <dd>Maximum depth of entries to display in the TOC.<br/>
051 * Positive int, not mandatory, 5 by default.</dd>
052 * </dl>
053 * For instance, in an APT file, you could write:
054 * <dl>
055 * <dt>%{toc|section=2|fromDepth=2|toDepth=3}</dt>
056 * <dd>Display a TOC for the second section in the document, including all
057 * subsections (depth 2) and  sub-subsections (depth 3).</dd>
058 * <dt>%{toc}</dt>
059 * <dd>display a TOC with all section and subsections
060 * (similar to %{toc|section=0} )</dd>
061 * </dl>
062 * Moreover, you need to write APT link for section to allow anchor,
063 * for instance:
064 * <pre>
065 * * {SubSection 1}
066 * </pre>
067 *
068 * Similarly, in an XDOC file, you could write:
069 * <pre>
070 * &lt;macro name="toc"&gt;
071 *   &lt;param name="section" value="1" /&gt;
072 *   &lt;param name="fromDepth" value="1" /&gt;
073 *   &lt;param name="toDepth" value="2" /&gt;
074 * &lt;/macro&gt;
075 * </pre>
076 *
077 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
078 * @version $Id$
079 */
080@Component( role = Macro.class, hint = "toc" )
081public class TocMacro
082    extends AbstractMacro
083{
084    /** The section to display. */
085    private int section;
086
087    /** Start depth. */
088    private int fromDepth;
089
090    /** End depth. */
091    private int toDepth;
092
093    /** The default end depth. */
094    private static final int DEFAULT_DEPTH = 5;
095
096    /** {@inheritDoc} */
097    public void execute( Sink sink, MacroRequest request )
098        throws MacroExecutionException
099    {
100        String source = request.getSourceContent();
101        Parser parser = request.getParser();
102
103        section = getInt( request, "section", 0 );
104        fromDepth = getInt( request, "fromDepth", 0 );
105        toDepth = getInt( request, "toDepth", DEFAULT_DEPTH );
106
107        if ( fromDepth > toDepth )
108        {
109            return;
110        }
111
112        IndexEntry index = new IndexEntry( "index" );
113        IndexingSink tocSink = new IndexingSink( index );
114
115        try
116        {
117            parser.parse( new StringReader( source ), tocSink );
118        }
119        catch ( ParseException e )
120        {
121            throw new MacroExecutionException( "ParseException: " + e.getMessage(), e );
122        }
123
124        if ( index.getChildEntries().size() > 0 )
125        {
126            sink.list( getAttributesFromMap( request.getParameters() ) );
127
128            int i = 1;
129
130            for ( IndexEntry sectionIndex : index.getChildEntries() )
131            {
132                if ( ( i == section ) || ( section == 0 ) )
133                {
134                    writeSubSectionN( sink, sectionIndex, 1 );
135                }
136
137                i++;
138            }
139
140            sink.list_();
141        }
142    }
143
144    /**
145     * @param sink The sink to write to.
146     * @param sectionIndex The section index.
147     * @param n The toc depth.
148     */
149    private void writeSubSectionN( Sink sink, IndexEntry sectionIndex, int n )
150    {
151        if ( fromDepth <= n )
152        {
153            sink.listItem();
154            sink.link( "#" + HtmlTools.encodeId( sectionIndex.getId() ) );
155            sink.text( sectionIndex.getTitle() );
156            sink.link_();
157        }
158
159        if ( toDepth > n )
160        {
161            if ( sectionIndex.getChildEntries().size() > 0 )
162            {
163                if ( fromDepth <= n )
164                {
165                    sink.list();
166                }
167
168                for ( IndexEntry subsectionIndex : sectionIndex.getChildEntries() )
169                {
170                    if ( n == toDepth - 1 )
171                    {
172                        sink.listItem();
173                        sink.link( "#" + HtmlTools.encodeId( subsectionIndex.getId() ) );
174                        sink.text( subsectionIndex.getTitle() );
175                        sink.link_();
176                        sink.listItem_();
177                    }
178                    else
179                    {
180                        writeSubSectionN( sink, subsectionIndex, n + 1 );
181                    }
182                }
183
184                if ( fromDepth <= n )
185                {
186                    sink.list_();
187                }
188            }
189        }
190
191        if ( fromDepth <= n )
192        {
193            sink.listItem_();
194        }
195    }
196
197    /**
198     * @param request The MacroRequest.
199     * @param parameter The parameter.
200     * @param defaultValue the default value.
201     * @return the int value of a parameter in the request.
202     * @throws MacroExecutionException if something goes wrong.
203     */
204    private static int getInt( MacroRequest request, String parameter, int defaultValue )
205        throws MacroExecutionException
206    {
207        String value = (String) request.getParameter( parameter );
208
209        if ( StringUtils.isEmpty( value ) )
210        {
211            return defaultValue;
212        }
213
214        int i;
215
216        try
217        {
218            i = Integer.parseInt( value );
219        }
220        catch ( NumberFormatException e )
221        {
222            return defaultValue;
223        }
224
225        if ( i < 0 )
226        {
227            throw new MacroExecutionException( "The " + parameter + "=" + i + " should be positive." );
228        }
229
230        return i;
231    }
232}