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 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 writeTocForIndexEntry(sink, getAttributesFromMap(request.getParameters()), tocSink.getRootEntry()); 116 } 117 118 void writeTocForIndexEntry(Sink sink, SinkEventAttributes listAttributes, IndexEntry rootEntry) { 119 IndexEntry index = rootEntry; 120 if (index.getChildEntries().size() > 0) { 121 sink.list(listAttributes); 122 123 int i = 1; 124 125 for (IndexEntry sectionIndex : index.getChildEntries()) { 126 if ((i == section) || (section == 0)) { 127 writeSubSectionN(sink, sectionIndex, 1); 128 } 129 130 i++; 131 } 132 133 sink.list_(); 134 } 135 } 136 137 /** 138 * This recursive method just skips index entries that are not sections (but still evaluates their children). 139 * @param sink The sink to write to. 140 * @param sectionIndex The section index. 141 * @param n The toc depth. 142 */ 143 private void writeSubSectionN(Sink sink, IndexEntry sectionIndex, int n) { 144 boolean isRelevantIndex = isRelevantIndexEntry(sectionIndex); 145 if (fromDepth <= n && isRelevantIndex) { 146 sink.listItem(); 147 sink.link("#" + DoxiaUtils.encodeId(sectionIndex.getId())); 148 sink.text(sectionIndex.getTitle()); 149 sink.link_(); 150 } 151 152 if (toDepth > n) { 153 if (sectionIndex.getChildEntries().size() > 0) { 154 if (fromDepth <= n && isRelevantIndex) { 155 sink.list(); 156 } 157 158 for (IndexEntry subsectionIndex : sectionIndex.getChildEntries()) { 159 if (n == toDepth - 1 && isRelevantIndex) { 160 sink.listItem(); 161 sink.link("#" + DoxiaUtils.encodeId(subsectionIndex.getId())); 162 sink.text(subsectionIndex.getTitle()); 163 sink.link_(); 164 sink.listItem_(); 165 } else { 166 writeSubSectionN(sink, subsectionIndex, n + 1); 167 } 168 } 169 170 if (fromDepth <= n && isRelevantIndex) { 171 sink.list_(); 172 } 173 } 174 } 175 176 if (fromDepth <= n && isRelevantIndex) { 177 sink.listItem_(); 178 } 179 } 180 181 static boolean isRelevantIndexEntry(IndexEntry indexEntry) { 182 return indexEntry.hasId() && indexEntry.getType().isSection(); 183 } 184 185 /** 186 * @param request The MacroRequest. 187 * @param parameter The parameter. 188 * @param defaultValue the default value. 189 * @return the int value of a parameter in the request. 190 * @throws MacroExecutionException if something goes wrong. 191 */ 192 private static int getInt(MacroRequest request, String parameter, int defaultValue) throws MacroExecutionException { 193 String value = (String) request.getParameter(parameter); 194 195 if (value == null || value.isEmpty()) { 196 return defaultValue; 197 } 198 199 int i; 200 201 try { 202 i = Integer.parseInt(value); 203 } catch (NumberFormatException e) { 204 return defaultValue; 205 } 206 207 if (i < 0) { 208 throw new MacroExecutionException("The " + parameter + "=" + i + " should be positive."); 209 } 210 211 return i; 212 } 213}