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 */ 019 package org.apache.wiki.plugin; 020 021 import java.io.IOException; 022 import java.io.StringReader; 023 import java.util.Map; 024 import java.util.ResourceBundle; 025 026 import org.apache.log4j.Logger; 027 import org.apache.wiki.InternalWikiException; 028 import org.apache.wiki.WikiContext; 029 import org.apache.wiki.WikiEngine; 030 import org.apache.wiki.WikiPage; 031 import org.apache.wiki.api.engine.FilterManager; 032 import org.apache.wiki.api.exceptions.PluginException; 033 import org.apache.wiki.api.plugin.WikiPlugin; 034 import org.apache.wiki.parser.Heading; 035 import org.apache.wiki.parser.HeadingListener; 036 import org.apache.wiki.parser.JSPWikiMarkupParser; 037 import org.apache.wiki.preferences.Preferences; 038 import org.apache.wiki.util.TextUtil; 039 040 /** 041 * Provides a table of contents. 042 * <p>Parameters : </p> 043 * <ul> 044 * <li><b>title</b> - The title of the table of contents.</li> 045 * <li><b>numbered</b> - if true, generates automatically numbers for the headings.</li> 046 * <li><b>start</b> - If using a numbered list, sets the start number.</li> 047 * <li><b>prefix</b> - If using a numbered list, sets the prefix used for the list.</li> 048 * </ul> 049 * 050 * @since 2.2 051 */ 052 public class TableOfContents 053 implements WikiPlugin, HeadingListener 054 { 055 private static Logger log = Logger.getLogger( TableOfContents.class ); 056 057 /** Parameter name for setting the title. */ 058 public static final String PARAM_TITLE = "title"; 059 060 /** Parameter name for setting whether the headings should be numbered. */ 061 public static final String PARAM_NUMBERED = "numbered"; 062 063 /** Parameter name for setting where the numbering should start. */ 064 public static final String PARAM_START = "start"; 065 066 /** Parameter name for setting what the prefix for the heading is. */ 067 public static final String PARAM_PREFIX = "prefix"; 068 069 private static final String VAR_ALREADY_PROCESSING = "__TableOfContents.processing"; 070 071 StringBuffer m_buf = new StringBuffer(); 072 private boolean m_usingNumberedList = false; 073 private String m_prefix = ""; 074 private int m_starting = 0; 075 private int m_level1Index = 0; 076 private int m_level2Index = 0; 077 private int m_level3Index = 0; 078 private int m_lastLevel = 0; 079 080 /** 081 * {@inheritDoc} 082 */ 083 public void headingAdded( WikiContext context, Heading hd ) 084 { 085 log.debug("HD: "+hd.m_level+", "+hd.m_titleText+", "+hd.m_titleAnchor); 086 087 switch( hd.m_level ) 088 { 089 case Heading.HEADING_SMALL: 090 m_buf.append("<li class=\"toclevel-3\">"); 091 m_level3Index++; 092 break; 093 case Heading.HEADING_MEDIUM: 094 m_buf.append("<li class=\"toclevel-2\">"); 095 m_level2Index++; 096 break; 097 case Heading.HEADING_LARGE: 098 m_buf.append("<li class=\"toclevel-1\">"); 099 m_level1Index++; 100 break; 101 default: 102 throw new InternalWikiException("Unknown depth in toc! (Please submit a bug report.)"); 103 } 104 105 if (m_level1Index < m_starting) 106 { 107 // in case we never had a large heading ... 108 m_level1Index++; 109 } 110 if ((m_lastLevel == Heading.HEADING_SMALL) && (hd.m_level != Heading.HEADING_SMALL)) 111 { 112 m_level3Index = 0; 113 } 114 if ( ((m_lastLevel == Heading.HEADING_SMALL) || (m_lastLevel == Heading.HEADING_MEDIUM)) && 115 (hd.m_level == Heading.HEADING_LARGE) ) 116 { 117 m_level3Index = 0; 118 m_level2Index = 0; 119 } 120 121 String titleSection = hd.m_titleSection.replace( '%', '_' ); 122 String pageName = context.getEngine().encodeName(context.getPage().getName()).replace( '%', '_' ); 123 124 String sectref = "#section-"+pageName+"-"+titleSection; 125 126 m_buf.append( "<a class=\"wikipage\" href=\"" + sectref + "\">" ); 127 if (m_usingNumberedList) 128 { 129 switch( hd.m_level ) 130 { 131 case Heading.HEADING_SMALL: 132 m_buf.append(m_prefix + m_level1Index + "." + m_level2Index + "."+ m_level3Index +" "); 133 break; 134 case Heading.HEADING_MEDIUM: 135 m_buf.append(m_prefix + m_level1Index + "." + m_level2Index + " "); 136 break; 137 case Heading.HEADING_LARGE: 138 m_buf.append(m_prefix + m_level1Index +" "); 139 break; 140 default: 141 throw new InternalWikiException("Unknown depth in toc! (Please submit a bug report.)"); 142 } 143 } 144 m_buf.append( TextUtil.replaceEntities(hd.m_titleText)+"</a></li>\n" ); 145 146 m_lastLevel = hd.m_level; 147 } 148 149 /** 150 * {@inheritDoc} 151 */ 152 public String execute( WikiContext context, Map<String, String> params ) 153 throws PluginException 154 { 155 WikiEngine engine = context.getEngine(); 156 WikiPage page = context.getPage(); 157 ResourceBundle rb = Preferences.getBundle( context, WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE ); 158 159 if( context.getVariable( VAR_ALREADY_PROCESSING ) != null ) 160 { 161 //return rb.getString("tableofcontents.title"); 162 return "<a href=\"#section-TOC\" class=\"toc\">"+rb.getString("tableofcontents.title")+"</a>"; 163 } 164 165 StringBuffer sb = new StringBuffer(); 166 167 sb.append("<div class=\"toc\">\n"); 168 sb.append("<div class=\"collapsebox\">\n"); 169 170 String title = params.get(PARAM_TITLE); 171 sb.append("<h4 id=\"section-TOC\">"); 172 if( title != null ) 173 { 174 //sb.append("<h4>"+TextUtil.replaceEntities(title)+"</h4>\n"); 175 sb.append(TextUtil.replaceEntities(title)); 176 } 177 else 178 { 179 //sb.append("<h4>"+rb.getString("tableofcontents.title")+"</h4>\n"); 180 sb.append(rb.getString("tableofcontents.title")); 181 } 182 sb.append("</h4>\n"); 183 184 // should we use an ordered list? 185 m_usingNumberedList = false; 186 if (params.containsKey(PARAM_NUMBERED)) 187 { 188 String numbered = params.get(PARAM_NUMBERED); 189 if (numbered.equalsIgnoreCase("true")) 190 { 191 m_usingNumberedList = true; 192 } 193 else if (numbered.equalsIgnoreCase("yes")) 194 { 195 m_usingNumberedList = true; 196 } 197 } 198 199 // if we are using a numbered list, get the rest of the parameters (if any) ... 200 if (m_usingNumberedList) 201 { 202 int start = 0; 203 String startStr = params.get(PARAM_START); 204 if ((startStr != null) && (startStr.matches("^\\d+$"))) 205 { 206 start = Integer.parseInt(startStr); 207 } 208 if (start < 0) start = 0; 209 210 m_starting = start; 211 m_level1Index = start - 1; 212 if (m_level1Index < 0) m_level1Index = 0; 213 m_level2Index = 0; 214 m_level3Index = 0; 215 m_prefix = params.get(PARAM_PREFIX); 216 if (m_prefix == null) m_prefix = ""; 217 m_lastLevel = Heading.HEADING_LARGE; 218 } 219 220 try 221 { 222 String wikiText = engine.getPureText( page ); 223 boolean runFilters = 224 "true".equals(engine.getVariableManager().getValue(context,WikiEngine.PROP_RUNFILTERS,"true")); 225 226 try 227 { 228 if( runFilters ) 229 { 230 FilterManager fm = engine.getFilterManager(); 231 wikiText = fm.doPreTranslateFiltering( context, wikiText ); 232 } 233 } 234 catch(Exception e) 235 { 236 log.error("Could not construct table of contents: Filter Error", e); 237 throw new PluginException("Unable to construct table of contents (see logs)"); 238 } 239 240 context.setVariable( VAR_ALREADY_PROCESSING, "x" ); 241 JSPWikiMarkupParser parser = new JSPWikiMarkupParser( context, 242 new StringReader(wikiText) ); 243 parser.addHeadingListener( this ); 244 245 parser.parse(); 246 247 sb.append( "<ul>\n"+m_buf.toString()+"</ul>\n" ); 248 } 249 catch( IOException e ) 250 { 251 log.error("Could not construct table of contents", e); 252 throw new PluginException("Unable to construct table of contents (see logs)"); 253 } 254 255 sb.append("</div>\n</div>\n"); 256 257 return sb.toString(); 258 } 259 260 }