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 * <macro name="toc">
71 * <param name="section" value="1" />
72 * <param name="fromDepth" value="1" />
73 * <param name="toDepth" value="2" />
74 * </macro>
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 }