public void headingAdded(WikiContext context, Heading hd) {
    log.debug("HD: " + hd.m_level + ", " + hd.m_titleText + ", " + hd.m_titleAnchor);

    switch (hd.m_level) {
      case Heading.HEADING_SMALL:
        m_buf.append("<li class=\"toclevel-3\">");
        m_level3Index++;
        break;
      case Heading.HEADING_MEDIUM:
        m_buf.append("<li class=\"toclevel-2\">");
        m_level2Index++;
        break;
      case Heading.HEADING_LARGE:
        m_buf.append("<li class=\"toclevel-1\">");
        m_level1Index++;
        break;
      default:
        throw new InternalWikiException("Unknown depth in toc! (Please submit a bug report.)");
    }

    if (m_level1Index < m_starting) {
      // in case we never had a large heading ...
      m_level1Index++;
    }
    if ((m_lastLevel == Heading.HEADING_SMALL) && (hd.m_level != Heading.HEADING_SMALL)) {
      m_level3Index = 0;
    }
    if (((m_lastLevel == Heading.HEADING_SMALL) || (m_lastLevel == Heading.HEADING_MEDIUM))
        && (hd.m_level == Heading.HEADING_LARGE)) {
      m_level3Index = 0;
      m_level2Index = 0;
    }

    String titleSection = hd.m_titleSection.replace('%', '_');
    String pageName = context.getEngine().encodeName(context.getPage().getName()).replace('%', '_');

    String url = context.getURL(WikiContext.VIEW, context.getPage().getName());
    String sectref = "#section-" + pageName + "-" + titleSection;

    m_buf.append("<a class=\"wikipage\" href=\"" + url + sectref + "\">");
    if (m_usingNumberedList) {
      switch (hd.m_level) {
        case Heading.HEADING_SMALL:
          m_buf.append(m_prefix + m_level1Index + "." + m_level2Index + "." + m_level3Index + " ");
          break;
        case Heading.HEADING_MEDIUM:
          m_buf.append(m_prefix + m_level1Index + "." + m_level2Index + " ");
          break;
        case Heading.HEADING_LARGE:
          m_buf.append(m_prefix + m_level1Index + " ");
          break;
        default:
          throw new InternalWikiException("Unknown depth in toc! (Please submit a bug report.)");
      }
    }
    m_buf.append(TextUtil.replaceEntities(hd.m_titleText) + "</a></li>\n");

    m_lastLevel = hd.m_level;
  }
  /**
   * Makes WikiText from a Collection.
   *
   * @param links Collection to make into WikiText.
   * @param separator Separator string to use.
   * @param numItems How many items to show.
   * @return The WikiText
   */
  protected String wikitizeCollection(Collection links, String separator, int numItems) {
    if (links == null || links.isEmpty()) return "";

    StringBuffer output = new StringBuffer();

    Iterator it = links.iterator();
    int count = 0;

    //
    //  The output will be B Item[1] A S B Item[2] A S B Item[3] A
    //
    while (it.hasNext() && ((count < numItems) || (numItems == ALL_ITEMS))) {
      String value = (String) it.next();

      if (count > 0) {
        output.append(m_after);
        output.append(m_separator);
      }

      output.append(m_before);

      // Make a Wiki markup link. See TranslatorReader.
      output.append("[" + m_engine.beautifyTitle(value) + "|" + value + "]");
      count++;
    }

    //
    //  Output final item - if there have been none, no "after" is printed
    //
    if (count > 0) output.append(m_after);

    return output.toString();
  }
  public String execute(WikiContext context, Map params) throws PluginException {
    WikiEngine engine = context.getEngine();
    WikiPage page = context.getPage();

    if (context.getVariable(VAR_ALREADY_PROCESSING) != null) return "Table of Contents";

    StringBuffer sb = new StringBuffer();

    sb.append("<div class=\"toc\">\n");
    sb.append("<div class=\"collapsebox\">\n");

    String title = (String) params.get(PARAM_TITLE);
    if (title != null) {
      sb.append("<h4>" + TextUtil.replaceEntities(title) + "</h4>\n");
    } else {
      sb.append("<h4>Table of Contents</h4>\n");
    }

    // should we use an ordered list?
    m_usingNumberedList = false;
    if (params.containsKey(PARAM_NUMBERED)) {
      String numbered = (String) params.get(PARAM_NUMBERED);
      if (numbered.equalsIgnoreCase("true")) {
        m_usingNumberedList = true;
      } else if (numbered.equalsIgnoreCase("yes")) {
        m_usingNumberedList = true;
      }
    }

    // if we are using a numbered list, get the rest of the parameters (if any) ...
    if (m_usingNumberedList) {
      int start = 0;
      String startStr = (String) params.get(PARAM_START);
      if ((startStr != null) && (startStr.matches("^\\d+$"))) {
        start = Integer.parseInt(startStr);
      }
      if (start < 0) start = 0;

      m_starting = start;
      m_level1Index = start - 1;
      if (m_level1Index < 0) m_level1Index = 0;
      m_level2Index = 0;
      m_level3Index = 0;
      m_prefix = (String) params.get(PARAM_PREFIX);
      if (m_prefix == null) m_prefix = "";
      m_lastLevel = Heading.HEADING_LARGE;
    }

    try {
      String wikiText = engine.getPureText(page);

      context.setVariable(VAR_ALREADY_PROCESSING, "x");
      JSPWikiMarkupParser parser = new JSPWikiMarkupParser(context, new StringReader(wikiText));
      parser.addHeadingListener(this);

      parser.parse();

      sb.append("<ul>\n" + m_buf.toString() + "</ul>\n");
    } catch (IOException e) {
      log.error("Could not construct table of contents", e);
      throw new PluginException("Unable to construct table of contents (see logs)");
    }

    sb.append("</div>\n</div>\n");

    return sb.toString();
  }