View Javadoc

1   package org.apache.maven.doxia.macro.toc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.StringReader;
23  
24  import org.apache.maven.doxia.index.IndexEntry;
25  import org.apache.maven.doxia.index.IndexingSink;
26  import org.apache.maven.doxia.macro.AbstractMacro;
27  import org.apache.maven.doxia.macro.Macro;
28  import org.apache.maven.doxia.macro.MacroExecutionException;
29  import org.apache.maven.doxia.macro.MacroRequest;
30  import org.apache.maven.doxia.util.HtmlTools;
31  import org.apache.maven.doxia.parser.ParseException;
32  import org.apache.maven.doxia.parser.Parser;
33  import org.apache.maven.doxia.sink.Sink;
34  
35  import org.codehaus.plexus.component.annotations.Component;
36  import org.codehaus.plexus.util.StringUtils;
37  
38  /**
39   * Macro to display a <code>Table Of Content</code> in a given <code>Sink</code>.
40   * The input parameters for this macro are:
41   * <dl>
42   * <dt>section</dt>
43   * <dd>Display a TOC for the specified section only, or all sections if 0.<br/>
44   * Positive int, not mandatory, 0 by default.</dd>
45   * <dt>fromDepth</dt>
46   * <dd>Minimal depth of entries to display in the TOC.
47   * Sections are depth 1, sub-sections depth 2, etc.<br/>
48   * Positive int, not mandatory, 0 by default.</dd>
49   * <dt>toDepth</dt>
50   * <dd>Maximum depth of entries to display in the TOC.<br/>
51   * Positive int, not mandatory, 5 by default.</dd>
52   * </dl>
53   * For instance, in an APT file, you could write:
54   * <dl>
55   * <dt>%{toc|section=2|fromDepth=2|toDepth=3}</dt>
56   * <dd>Display a TOC for the second section in the document, including all
57   * subsections (depth 2) and  sub-subsections (depth 3).</dd>
58   * <dt>%{toc}</dt>
59   * <dd>display a TOC with all section and subsections
60   * (similar to %{toc|section=0} )</dd>
61   * </dl>
62   * Moreover, you need to write APT link for section to allow anchor,
63   * for instance:
64   * <pre>
65   * * {SubSection 1}
66   * </pre>
67   *
68   * Similarly, in an XDOC file, you could write:
69   * <pre>
70   * &lt;macro name="toc"&gt;
71   *   &lt;param name="section" value="1" /&gt;
72   *   &lt;param name="fromDepth" value="1" /&gt;
73   *   &lt;param name="toDepth" value="2" /&gt;
74   * &lt;/macro&gt;
75   * </pre>
76   *
77   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
78   * @version $Id: TocMacro.java 1345590 2012-06-02 21:36:10Z hboutemy $
79   */
80  @Component( role = Macro.class, hint = "toc" )
81  public class TocMacro
82      extends AbstractMacro
83  {
84      /** The section to display. */
85      private int section;
86  
87      /** Start depth. */
88      private int fromDepth;
89  
90      /** End depth. */
91      private int toDepth;
92  
93      /** The default end depth. */
94      private static final int DEFAULT_DEPTH = 5;
95  
96      /** {@inheritDoc} */
97      public void execute( Sink sink, MacroRequest request )
98          throws MacroExecutionException
99      {
100         String source = (String) request.getParameter( "sourceContent" );
101         Parser parser = (Parser) request.getParameter( "parser" );
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 }