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 * <macro name="toc"> 071 * <param name="section" value="1" /> 072 * <param name="fromDepth" value="1" /> 073 * <param name="toDepth" value="2" /> 074 * </macro> 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 = (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}