Table of Contents Macro - Add Minimum Depth Parameter

2. Short description

You can specify max depth with the TableOfContents macro, which will look like:

1. First-Level Heading 1
  1. Second-Level Heading 1
  2. Second-Level Heading 2
    1. Third-Level Heading 1
    2. Third-Level Heading 2

However I would like it to look like:

1. Second-Level Heading 1
2. Second-Level Heading 2

Is this currently possible?

Possible syntax would be:

[[TableOfContents([mindepth][,maxdepth])]]

eg. [[TableOfContents(2,2)]] to produce the desirable table of contents above.

3. Discussion

I would like to vote for this feature. With version 1.5, the page name is no longer automatically placed at the top as a title. So we've begun using first-level headings to manually add a title. Problem is, that gets included in the TOC, which is not helpful. A minimum depth would solve the issue. (An alternate solution to my particular problem would be a PageTitle() macro, which would generate a large-font title that is not interpreted as a heading. Even so, the minimum depth parameter would be useful in the table of contents.) -- SteveDavison 2006-06-16 18:37:24

-- Why not automatically exclude the level 1 header if there only exists one and it is equal to the page title?

4. Small Patch

-- KeithSchwols: I took a whack at this tonight. My first concern was not to break any existing functionality. The TOC code pretty much assumes that the first parameter is numeric and it the maxdepth. This is based on the code

I also note (for those interested), the the starting depth shown in the table of contents is controlled via the section-numbers pragma. If you set #pragma section-numbers 2 at the top of the page. You will never see the top heading (level 1) in your table of contents. Of course, this comes as the price that you now have numbering on all your heading below that level.

Anyway, I choose to implement the following syntax

[[TableOfContents([mindepth=mindepth][,maxdepth=maxdepth])]]

eg. [[TableOfContents(mindepth=2,maxdepth=3)]]

and continue to support
[[TableOfContents(6)]] to be understood as requesting "maxdepth=6"

Ideally, I would like to force parameters named, but that would break many existing pages (if that ever gets patched to MoinMoin source, then a convert script will have to be written to find all macro calls and add "maxdepth=" in front of single numeric parameters).

So, to make my quick fix work, I patched the following changes into the TableOfContents.py file.

This applies against the 1.5.4 version:

Index: TableOfContents.py
===================================================================
--- TableOfContents.py  (revision 116)
+++ TableOfContents.py  (working copy)
@@ -34,7 +34,15 @@
 class TableOfContents:
     """
     TOC Macro wraps all global variables without disturbing threads
+
+    TOC macro used to have single args, a number to indicate maxdepth
+    We're extending the interface to specify mindepth and maxdepth.
+    Any TOC use that consisting of only a # in the parameter is considered legacy
+    We don't want to break existing wikis, but all new users should use named parameters
+    @mindepth int  = ignore all headings < '#' when building TOC
+    @maxdepth int  = don't use any headings > '#' when building TOC
     """
+    arguments = ['mindepth', 'maxdepth', ]

     def __init__(self, macro, args):
         self.macro = macro
@@ -52,17 +60,38 @@
         self.titles = {}

         self.include_macro = None
-
+        arg_dict = self.getArgs(args, self.arguments)
+        self.maxdepth = 99
         try:
             self.mindepth = int(macro.request.getPragma('section-numbers', 1))
         except (ValueError, TypeError):
             self.mindepth = 1

-        try:
+        try:  # First check if we are a legacy macro call
             self.maxdepth = max(int(args), 1)
         except (ValueError, TypeError):
-            self.maxdepth = 99
+            self.mindepth = int(arg_dict.get("mindepth", self.mindepth))
+            self.maxdepth = int(arg_dict.get("maxdepth", self.maxdepth))

+    def getArgs(self, string, arguments):
+        """
+        @param string: string from the wiki markup [[NewPage(string)]]
+        @rtype: dict
+        @return: dictionary with macro options
+        """
+        if not string:
+            return {}
+        args = {}
+        for s in string.split(','):
+            if s and s.find('=') > 0:
+                key, value = s.split('=', 1)
+                if key :
+                    key = key.strip()
+                    key = key.lower()
+                if key and value and key in arguments :
+                    args[str(key.strip())] = value.strip()
+        return args
+
     def IncludeMacro(self, *args, **kwargs):
         if self.include_macro is None:
             self.include_macro = wikiutil.importPlugin(self.macro.request.cfg,

So a couple of things.

  1. I preserved the #pragma behavior, but if the Macro specifies a value that overrides the pragma. I'm not sure in Moin if anyone has established a precedent for which binds greater pramgas or macro/parser behavior.
  2. I tested this in my firewalled wiki and it's working for us. However, we don't do very strange or unusual nesting of Include'ed pages - so I don't know how this will behave. YMMV

4.1. Further Comments

I've been using this for a while and haven't had any problems. I really would like to see this applied to the upcoming 1.5.5/1.6 branches. How should I got about driving this?

TODO before we can apply that patch to main branch:

5. Alternative Moin 1.7.0 Patch

The patch below adds exclude-depth as a second parameter to the <<TableOfContents>> macro. The default level of 1 (<<TableOfContents(5,1)>>) is probably now the most standard case -- since Moin 1.5 users have begun using a single level 1 heading as a page title. To avoid the counter-intuitive use of (max, min) or (5,2) discussed above, the solution avoids the term minimum in favor of exclude: (include-levels, exclude levels) or (5,1).

An error message is generated should a user accidentally reverse the parameters and put the exclude before the include.

An earlier version of this patch changed the heading created by the macro to Table of Contents rather than Contents. After the patch was implemented a debate ensued and a survey of books was undertaken. About 80% of the books in my library use "Contents". This suggests the heading is correct, the macro name is wrong. Yet another solution may be to create a new Contents macro using the parameters (min, max) with the defaults (2,5).

Any solution that allows removal of the page title from the Contents will work for me.

The patch applies to /macro.TableOfContents.py of Moin release 1.7.0.

Index: TableOfContents.py
===================================================================
--- TableOfContents.py  (revision xxx)
+++ TableOfContents.py  (working copy)
@@ -131,15 +131,19 @@
     comment = _anything_return_empty
     transclusion = _anything_return_empty

-def macro_TableOfContents(macro, maxdepth=int):
+def macro_TableOfContents(macro, maxdepth=int,excludedepth=int):
     """
 Prints a table of contents.

- maxdepth:: maximum depth the table of contents is generated for (defaults to unlimited)
+ maxdepth:: maximum depth of the table of contents (defaults to unlimited)
+ excludedepth:: levels to be excluded from table of contents (defaults to 1 to exclude page title)
     """
+
     if maxdepth is None:
         maxdepth = 99
-
+    if excludedepth is None:
+        excludedepth = 1
+
     pname = macro.formatter.page.page_name

     macro.request.push_unique_ids()
@@ -162,6 +166,10 @@
         macro.formatter.text(_('Contents')),
         macro.formatter.paragraph(0),
     ]
+
+    if maxdepth <= excludedepth or maxdepth < 1 or excludedepth < 0 or excludedepth > 4:
+        result.append(_(" - Invalid parameters to TOC macro, defaulting to (5,1)"))
+        maxdepth, excludedepth = 5, 1

     lastlvl = 0

@@ -171,6 +179,9 @@
             continue
         if lvl > maxdepth or id is None:
             continue
+        lvl -= excludedepth
+        if lvl < 1:
+            continue

         # will be reset by pop_unique_ids below
         macro.request.include_id = incl_id

5.1. Pages With Broken Table of Contents

With the above patch applied to 1.7.0, a quick check of the underlay pages shows the TOC on most pages display correctly, e.g. there was only one level-1 heading and that was the page title. In addition, there were a few pages with unusual heading structures that were not helped nor hurt by the mod.

The pages found with multiple level 1 headings were:

6. Minimal Patch

Unlike patches which try and redefine the arguments, I've gone for the minimal approach which adds another argument and makes the first argument dual purpose (which is a bit like the Python range function and other built-in functions and types in Python). I've attached it here.

With this patch applied the usual macro invocations work as before, but by supplying two arguments a minimum and maximum heading depth is explicitly specified:

<<TableOfContents(2,2)>>

This will only populate the table with level 2 headings.


CategoryFeatureRequest CategoryForMoin2 CategoryFeaturePatched

MoinMoin: FeatureRequests/TableOfContentsMinDepth (last edited 2011-02-05 00:32:41 by PaulBoddie)