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.MacroExecutionException;
28 import org.apache.maven.doxia.macro.MacroRequest;
29 import org.apache.maven.doxia.util.HtmlTools;
30 import org.apache.maven.doxia.parser.ParseException;
31 import org.apache.maven.doxia.parser.Parser;
32 import org.apache.maven.doxia.sink.Sink;
33
34 import org.codehaus.plexus.util.StringUtils;
35
36 /**
37 * Macro to display a <code>Table Of Content</code> in a given <code>Sink</code>.
38 * The input parameters for this macro are:
39 * <dl>
40 * <dt>section</dt>
41 * <dd>Display a TOC for the specified section only, or all sections if 0.<br/>
42 * Positive int, not mandatory, 0 by default.</dd>
43 * <dt>fromDepth</dt>
44 * <dd>Minimal depth of entries to display in the TOC.
45 * Sections are depth 1, sub-sections depth 2, etc.<br/>
46 * Positive int, not mandatory, 0 by default.</dd>
47 * <dt>toDepth</dt>
48 * <dd>Maximum depth of entries to display in the TOC.<br/>
49 * Positive int, not mandatory, 5 by default.</dd>
50 * </dl>
51 * For instance, in an APT file, you could write:
52 * <dl>
53 * <dt>%{toc|section=2|fromDepth=2|toDepth=3}</dt>
54 * <dd>Display a TOC for the second section in the document, including all
55 * subsections (depth 2) and sub-subsections (depth 3).</dd>
56 * <dt>%{toc}</dt>
57 * <dd>display a TOC with all section and subsections
58 * (similar to %{toc|section=0} )</dd>
59 * </dl>
60 * Moreover, you need to write APT link for section to allow anchor,
61 * for instance:
62 * <pre>
63 * * {SubSection 1}
64 * </pre>
65 *
66 * Similarly, in an XDOC file, you could write:
67 * <pre>
68 * <macro name="toc">
69 * <param name="section" value="1" />
70 * <param name="fromDepth" value="1" />
71 * <param name="toDepth" value="2" />
72 * </macro>
73 * </pre>
74 *
75 * @plexus.component role-hint="toc"
76 *
77 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
78 * @version $Id: TocMacro.java 1090706 2011-04-09 23:15:28Z hboutemy $
79 */
80 public class TocMacro
81 extends AbstractMacro
82 {
83 /** The section to display. */
84 private int section;
85
86 /** Start depth. */
87 private int fromDepth;
88
89 /** End depth. */
90 private int toDepth;
91
92 /** The default end depth. */
93 private static final int DEFAULT_DEPTH = 5;
94
95 /** {@inheritDoc} */
96 public void execute( Sink sink, MacroRequest request )
97 throws MacroExecutionException
98 {
99 String source = (String) request.getParameter( "sourceContent" );
100 Parser parser = (Parser) request.getParameter( "parser" );
101
102 section = getInt( request, "section", 0 );
103 fromDepth = getInt( request, "fromDepth", 0 );
104 toDepth = getInt( request, "toDepth", DEFAULT_DEPTH );
105
106 if ( fromDepth > toDepth )
107 {
108 return;
109 }
110
111 IndexEntry index = new IndexEntry( "index" );
112 IndexingSink tocSink = new IndexingSink( index );
113
114 try
115 {
116 parser.parse( new StringReader( source ), tocSink );
117 }
118 catch ( ParseException e )
119 {
120 throw new MacroExecutionException( "ParseException: " + e.getMessage(), e );
121 }
122
123 if ( index.getChildEntries().size() > 0 )
124 {
125 sink.list( getAttributesFromMap( request.getParameters() ) );
126
127 int i = 1;
128
129 for ( IndexEntry sectionIndex : index.getChildEntries() )
130 {
131 if ( ( i == section ) || ( section == 0 ) )
132 {
133 writeSubSectionN( sink, sectionIndex, 1 );
134 }
135
136 i++;
137 }
138
139 sink.list_();
140 }
141 }
142
143 /**
144 * @param sink The sink to write to.
145 * @param sectionIndex The section index.
146 * @param n The toc depth.
147 */
148 private void writeSubSectionN( Sink sink, IndexEntry sectionIndex, int n )
149 {
150 if ( fromDepth <= n )
151 {
152 sink.listItem();
153 sink.link( "#" + HtmlTools.encodeId( sectionIndex.getId() ) );
154 sink.text( sectionIndex.getTitle() );
155 sink.link_();
156 }
157
158 if ( toDepth > n )
159 {
160 if ( sectionIndex.getChildEntries().size() > 0 )
161 {
162 if ( fromDepth <= n )
163 {
164 sink.list();
165 }
166
167 for ( IndexEntry subsectionIndex : sectionIndex.getChildEntries() )
168 {
169 if ( n == toDepth - 1 )
170 {
171 sink.listItem();
172 sink.link( "#" + HtmlTools.encodeId( subsectionIndex.getId() ) );
173 sink.text( subsectionIndex.getTitle() );
174 sink.link_();
175 sink.listItem_();
176 }
177 else
178 {
179 writeSubSectionN( sink, subsectionIndex, n + 1 );
180 }
181 }
182
183 if ( fromDepth <= n )
184 {
185 sink.list_();
186 }
187 }
188 }
189
190 if ( fromDepth <= n )
191 {
192 sink.listItem_();
193 }
194 }
195
196 /**
197 * @param request The MacroRequest.
198 * @param parameter The parameter.
199 * @param defaultValue the default value.
200 * @return the int value of a parameter in the request.
201 * @throws MacroExecutionException if something goes wrong.
202 */
203 private static int getInt( MacroRequest request, String parameter, int defaultValue )
204 throws MacroExecutionException
205 {
206 String value = (String) request.getParameter( parameter );
207
208 if ( StringUtils.isEmpty( value ) )
209 {
210 return defaultValue;
211 }
212
213 int i;
214
215 try
216 {
217 i = Integer.parseInt( value );
218 }
219 catch ( NumberFormatException e )
220 {
221 return defaultValue;
222 }
223
224 if ( i < 0 )
225 {
226 throw new MacroExecutionException( "The " + parameter + "=" + i + " should be positive." );
227 }
228
229 return i;
230 }
231 }