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.SinkEventAttributes; 035import org.apache.maven.doxia.sink.impl.SinkAdapter; 036import org.apache.maven.doxia.util.DoxiaUtils; 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 */ 079@Singleton 080@Named("toc") 081public class TocMacro extends AbstractMacro { 082 /** The section to display. */ 083 private int section; 084 085 /** Start depth. */ 086 private int fromDepth; 087 088 /** End depth. */ 089 private int toDepth = DEFAULT_DEPTH; 090 091 /** The default end depth. */ 092 private static final int DEFAULT_DEPTH = 5; 093 094 /** {@inheritDoc} */ 095 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 096 String source = request.getSourceContent(); 097 Parser parser = request.getParser(); 098 099 section = getInt(request, "section", 0); 100 fromDepth = getInt(request, "fromDepth", 0); 101 toDepth = getInt(request, "toDepth", DEFAULT_DEPTH); 102 103 if (fromDepth > toDepth) { 104 return; 105 } 106 107 IndexingSink tocSink = new IndexingSink(new SinkAdapter()); 108 try { 109 parser.parse(new StringReader(source), tocSink); 110 } catch (ParseException e) { 111 throw new MacroExecutionException(e); 112 } finally { 113 tocSink.close(); 114 } 115 116 writeTocForIndexEntry(sink, getAttributesFromMap(request.getParameters()), tocSink.getRootEntry()); 117 } 118 119 void writeTocForIndexEntry(Sink sink, SinkEventAttributes listAttributes, IndexEntry rootEntry) { 120 IndexEntry index = rootEntry; 121 if (index.getChildEntries().size() > 0) { 122 sink.list(listAttributes); 123 124 int i = 1; 125 126 for (IndexEntry sectionIndex : index.getChildEntries()) { 127 if ((i == section) || (section == 0)) { 128 writeSubSectionN(sink, sectionIndex, 1); 129 } 130 131 i++; 132 } 133 134 sink.list_(); 135 } 136 } 137 138 /** 139 * This recursive method just skips index entries that are not sections (but still evaluates their children). 140 * @param sink The sink to write to. 141 * @param sectionIndex The section index. 142 * @param n The toc depth. 143 */ 144 private void writeSubSectionN(Sink sink, IndexEntry sectionIndex, int n) { 145 boolean isRelevantIndex = isRelevantIndexEntry(sectionIndex); 146 if (fromDepth <= n && isRelevantIndex) { 147 sink.listItem(); 148 sink.link("#" + DoxiaUtils.encodeId(sectionIndex.getId())); 149 sink.text(sectionIndex.getTitle()); 150 sink.link_(); 151 } 152 153 if (toDepth > n) { 154 if (sectionIndex.getChildEntries().size() > 0) { 155 if (fromDepth <= n && isRelevantIndex) { 156 sink.list(); 157 } 158 159 for (IndexEntry subsectionIndex : sectionIndex.getChildEntries()) { 160 if (n == toDepth - 1 && isRelevantIndex) { 161 sink.listItem(); 162 sink.link("#" + DoxiaUtils.encodeId(subsectionIndex.getId())); 163 sink.text(subsectionIndex.getTitle()); 164 sink.link_(); 165 sink.listItem_(); 166 } else { 167 writeSubSectionN(sink, subsectionIndex, n + 1); 168 } 169 } 170 171 if (fromDepth <= n && isRelevantIndex) { 172 sink.list_(); 173 } 174 } 175 } 176 177 if (fromDepth <= n && isRelevantIndex) { 178 sink.listItem_(); 179 } 180 } 181 182 static boolean isRelevantIndexEntry(IndexEntry indexEntry) { 183 return indexEntry.hasId() && indexEntry.getType().isSection(); 184 } 185 186 /** 187 * @param request The MacroRequest. 188 * @param parameter The parameter. 189 * @param defaultValue the default value. 190 * @return the int value of a parameter in the request. 191 * @throws MacroExecutionException if something goes wrong. 192 */ 193 private static int getInt(MacroRequest request, String parameter, int defaultValue) throws MacroExecutionException { 194 String value = (String) request.getParameter(parameter); 195 196 if (value == null || value.isEmpty()) { 197 return defaultValue; 198 } 199 200 int i; 201 202 try { 203 i = Integer.parseInt(value); 204 } catch (NumberFormatException e) { 205 return defaultValue; 206 } 207 208 if (i < 0) { 209 throw new MacroExecutionException("The " + parameter + "=" + i + " should be positive."); 210 } 211 212 return i; 213 } 214}