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 }