001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.doxia.macro.toc; 020 021import javax.inject.Named; 022import javax.inject.Singleton; 023 024import java.io.StringReader; 025 026import org.apache.maven.doxia.index.IndexEntry; 027import org.apache.maven.doxia.index.IndexingSink; 028import org.apache.maven.doxia.macro.AbstractMacro; 029import org.apache.maven.doxia.macro.MacroExecutionException; 030import org.apache.maven.doxia.macro.MacroRequest; 031import org.apache.maven.doxia.parser.ParseException; 032import org.apache.maven.doxia.parser.Parser; 033import org.apache.maven.doxia.sink.Sink; 034import org.apache.maven.doxia.sink.impl.SinkAdapter; 035import org.apache.maven.doxia.util.DoxiaUtils; 036 037/** 038 * Macro to display a <code>Table Of Content</code> in a given <code>Sink</code>. 039 * The input parameters for this macro are: 040 * <dl> 041 * <dt>section</dt> 042 * <dd>Display a TOC for the specified section only, or all sections if 0.<br> 043 * Positive int, not mandatory, 0 by default.</dd> 044 * <dt>fromDepth</dt> 045 * <dd>Minimal depth of entries to display in the TOC. 046 * Sections are depth 1, sub-sections depth 2, etc.<br> 047 * Positive int, not mandatory, 0 by default.</dd> 048 * <dt>toDepth</dt> 049 * <dd>Maximum depth of entries to display in the TOC.<br> 050 * Positive int, not mandatory, 5 by default.</dd> 051 * </dl> 052 * For instance, in an APT file, you could write: 053 * <dl> 054 * <dt>%{toc|section=2|fromDepth=2|toDepth=3}</dt> 055 * <dd>Display a TOC for the second section in the document, including all 056 * subsections (depth 2) and sub-subsections (depth 3).</dd> 057 * <dt>%{toc}</dt> 058 * <dd>display a TOC with all section and subsections 059 * (similar to %{toc|section=0})</dd> 060 * </dl> 061 * Moreover, you need to write APT link for section to allow anchor, 062 * for instance: 063 * <pre> 064 * * {SubSection 1} 065 * </pre> 066 * 067 * Similarly, in an XDOC file, you could write: 068 * <pre> 069 * <macro name="toc"> 070 * <param name="section" value="1" /> 071 * <param name="fromDepth" value="1" /> 072 * <param name="toDepth" value="2" /> 073 * </macro> 074 * </pre> 075 * 076 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> 077 */ 078@Singleton 079@Named("toc") 080public class TocMacro extends AbstractMacro { 081 /** The section to display. */ 082 private int section; 083 084 /** Start depth. */ 085 private int fromDepth; 086 087 /** End depth. */ 088 private int toDepth; 089 090 /** The default end depth. */ 091 private static final int DEFAULT_DEPTH = 5; 092 093 /** {@inheritDoc} */ 094 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 095 String source = request.getSourceContent(); 096 Parser parser = request.getParser(); 097 098 section = getInt(request, "section", 0); 099 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}