View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.doxia.macro.toc;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.StringReader;
25  
26  import org.apache.maven.doxia.index.IndexEntry;
27  import org.apache.maven.doxia.index.IndexingSink;
28  import org.apache.maven.doxia.macro.AbstractMacro;
29  import org.apache.maven.doxia.macro.MacroExecutionException;
30  import org.apache.maven.doxia.macro.MacroRequest;
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  import org.apache.maven.doxia.sink.impl.SinkAdapter;
35  import org.apache.maven.doxia.util.DoxiaUtils;
36  
37  /**
38   * Macro to display a <code>Table Of Content</code> in a given <code>Sink</code>.
39   * The input parameters for this macro are:
40   * <dl>
41   * <dt>section</dt>
42   * <dd>Display a TOC for the specified section only, or all sections if 0.<br>
43   * Positive int, not mandatory, 0 by default.</dd>
44   * <dt>fromDepth</dt>
45   * <dd>Minimal depth of entries to display in the TOC.
46   * Sections are depth 1, sub-sections depth 2, etc.<br>
47   * Positive int, not mandatory, 0 by default.</dd>
48   * <dt>toDepth</dt>
49   * <dd>Maximum depth of entries to display in the TOC.<br>
50   * Positive int, not mandatory, 5 by default.</dd>
51   * </dl>
52   * For instance, in an APT file, you could write:
53   * <dl>
54   * <dt>%{toc|section=2|fromDepth=2|toDepth=3}</dt>
55   * <dd>Display a TOC for the second section in the document, including all
56   * subsections (depth 2) and  sub-subsections (depth 3).</dd>
57   * <dt>%{toc}</dt>
58   * <dd>display a TOC with all section and subsections
59   * (similar to %{toc|section=0})</dd>
60   * </dl>
61   * Moreover, you need to write APT link for section to allow anchor,
62   * for instance:
63   * <pre>
64   * * {SubSection 1}
65   * </pre>
66   *
67   * Similarly, in an XDOC file, you could write:
68   * <pre>
69   * &lt;macro name="toc"&gt;
70   *   &lt;param name="section" value="1" /&gt;
71   *   &lt;param name="fromDepth" value="1" /&gt;
72   *   &lt;param name="toDepth" value="2" /&gt;
73   * &lt;/macro&gt;
74   * </pre>
75   *
76   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
77   */
78  @Singleton
79  @Named("toc")
80  public class TocMacro extends AbstractMacro {
81      /** The section to display. */
82      private int section;
83  
84      /** Start depth. */
85      private int fromDepth;
86  
87      /** End depth. */
88      private int toDepth;
89  
90      /** The default end depth. */
91      private static final int DEFAULT_DEPTH = 5;
92  
93      /** {@inheritDoc} */
94      public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
95          String source = request.getSourceContent();
96          Parser parser = request.getParser();
97  
98          section = getInt(request, "section", 0);
99          fromDepth = getInt(request, "fromDepth", 0);
100         toDepth = getInt(request, "toDepth", DEFAULT_DEPTH);
101 
102         if (fromDepth > toDepth) {
103             return;
104         }
105 
106         IndexingSink tocSink = new IndexingSink(new SinkAdapter());
107         try {
108             parser.parse(new StringReader(source), tocSink);
109         } catch (ParseException e) {
110             throw new MacroExecutionException(e);
111         } finally {
112             tocSink.close();
113         }
114 
115         IndexEntry index = tocSink.getRootEntry();
116         if (index.getChildEntries().size() > 0) {
117             sink.list(getAttributesFromMap(request.getParameters()));
118 
119             int i = 1;
120 
121             for (IndexEntry sectionIndex : index.getChildEntries()) {
122                 if ((i == section) || (section == 0)) {
123                     writeSubSectionN(sink, sectionIndex, 1);
124                 }
125 
126                 i++;
127             }
128 
129             sink.list_();
130         }
131     }
132 
133     /**
134      * @param sink The sink to write to.
135      * @param sectionIndex The section index.
136      * @param n The toc depth.
137      */
138     private void writeSubSectionN(Sink sink, IndexEntry sectionIndex, int n) {
139         if (fromDepth <= n) {
140             sink.listItem();
141             sink.link("#" + DoxiaUtils.encodeId(sectionIndex.getId()));
142             sink.text(sectionIndex.getTitle());
143             sink.link_();
144         }
145 
146         if (toDepth > n) {
147             if (sectionIndex.getChildEntries().size() > 0) {
148                 if (fromDepth <= n) {
149                     sink.list();
150                 }
151 
152                 for (IndexEntry subsectionIndex : sectionIndex.getChildEntries()) {
153                     if (n == toDepth - 1) {
154                         sink.listItem();
155                         sink.link("#" + DoxiaUtils.encodeId(subsectionIndex.getId()));
156                         sink.text(subsectionIndex.getTitle());
157                         sink.link_();
158                         sink.listItem_();
159                     } else {
160                         writeSubSectionN(sink, subsectionIndex, n + 1);
161                     }
162                 }
163 
164                 if (fromDepth <= n) {
165                     sink.list_();
166                 }
167             }
168         }
169 
170         if (fromDepth <= n) {
171             sink.listItem_();
172         }
173     }
174 
175     /**
176      * @param request The MacroRequest.
177      * @param parameter The parameter.
178      * @param defaultValue the default value.
179      * @return the int value of a parameter in the request.
180      * @throws MacroExecutionException if something goes wrong.
181      */
182     private static int getInt(MacroRequest request, String parameter, int defaultValue) throws MacroExecutionException {
183         String value = (String) request.getParameter(parameter);
184 
185         if (value == null || value.isEmpty()) {
186             return defaultValue;
187         }
188 
189         int i;
190 
191         try {
192             i = Integer.parseInt(value);
193         } catch (NumberFormatException e) {
194             return defaultValue;
195         }
196 
197         if (i < 0) {
198             throw new MacroExecutionException("The " + parameter + "=" + i + " should be positive.");
199         }
200 
201         return i;
202     }
203 }