Back to index

moin  1.9.0~rc2
TableOfContents.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - TableOfContents Macro
00004 
00005     The macro works as follows: First, it renders the page using
00006     the TOCFormatter (below) to get access to the outline of the
00007     page. During the page rendering, only macros whose
00008     'generates_headings' property is set and True are rendered,
00009     most macros don't generate any headings and thus need not be
00010     executed speeding up the process considerably.
00011 
00012     The generated outline is then written to the output.
00013 
00014     However, this is not all. Consider included pages that include
00015     a TOC themselves! First of all, TOCs don't generate headings
00016     so we avoid recursion during the collection process. Secondly,
00017     we always keep track of which content we are in and the
00018     formatter's heading method is responsible for making all
00019     IDs they generate unique. We use the same algorithm to make
00020     the IDs unique during the TOCFormatter rendering step so that
00021     in the end we can output the same IDs and the TOC is linked
00022     correctly, even in the case of multiple nested inclusions.
00023 
00024     @copyright: 2007 MoinMoin:JohannesBerg
00025     @license: GNU GPL, see COPYING for details.
00026 """
00027 
00028 from MoinMoin.formatter import FormatterBase
00029 from MoinMoin.Page import Page
00030 from MoinMoin import wikiutil
00031 
00032 
00033 # cannot be cached because of TOCs in included pages
00034 Dependencies = ['time']
00035 
00036 class TOCFormatter(FormatterBase):
00037     def __init__(self, request, **kw):
00038         FormatterBase.__init__(self, request, **kw)
00039         self.in_heading = False
00040         self.collected_headings = request._tocfm_collected_headings
00041 
00042     def _text(self, text):
00043         if self.in_heading:
00044             self.collected_headings[-1][2] += text
00045         return text
00046 
00047     def startContent(self, *args, **kw):
00048         res = FormatterBase.startContent(self, *args, **kw)
00049         self.collected_headings.append([1, self.request.uid_generator.include_id, None])
00050         return res
00051 
00052     def endContent(self):
00053         res = FormatterBase.endContent(self)
00054         self.collected_headings.append([0, self.request.uid_generator.include_id, None])
00055         return res
00056 
00057     def heading(self, on, depth, **kw):
00058         id = kw.get('id', None)
00059         self.in_heading = on
00060         if not id is None:
00061             id = self.request._tocfm_orig_formatter.make_id_unique(id)
00062         if on:
00063             self.collected_headings.append([depth, id, u''])
00064         return ''
00065 
00066     def macro(self, macro_obj, name, args, markup=None):
00067         try:
00068             # plugins that are defined in the macro class itself
00069             # can't generate headings this way, but that's fine
00070             gen_headings = wikiutil.importPlugin(self.request.cfg, 'macro',
00071                                                  name, 'generates_headings')
00072             if gen_headings:
00073                 return FormatterBase.macro(self, macro_obj, name, args, markup)
00074         except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError):
00075             pass
00076         return ''
00077 
00078     def _anything_return_empty(self, *args, **kw):
00079         return ''
00080 
00081     lang = _anything_return_empty
00082     sysmsg = _anything_return_empty
00083     startDocument = _anything_return_empty
00084     endDocument = _anything_return_empty
00085     pagelink = _anything_return_empty
00086     interwikilink = _anything_return_empty
00087     url = _anything_return_empty
00088     attachment_link = _anything_return_empty
00089     attachment_image = _anything_return_empty
00090     attachment_drawing = _anything_return_empty
00091     attachment_inlined = _anything_return_empty
00092     anchordef = _anything_return_empty
00093     line_anchordef = _anything_return_empty
00094     anchorlink = _anything_return_empty
00095     line_anchorlink = _anything_return_empty
00096     image = _anything_return_empty
00097     smiley = _anything_return_empty
00098     nowikiword = _anything_return_empty
00099     strong = _anything_return_empty
00100     emphasis = _anything_return_empty
00101     underline = _anything_return_empty
00102     highlight = _anything_return_empty
00103     sup = _anything_return_empty
00104     sub = _anything_return_empty
00105     strike = _anything_return_empty
00106     code = _anything_return_empty
00107     preformatted = _anything_return_empty
00108     small = _anything_return_empty
00109     big = _anything_return_empty
00110     code_area = _anything_return_empty
00111     code_line = _anything_return_empty
00112     code_token = _anything_return_empty
00113     linebreak = _anything_return_empty
00114     paragraph = _anything_return_empty
00115     rule = _anything_return_empty
00116     icon = _anything_return_empty
00117     number_list = _anything_return_empty
00118     bullet_list = _anything_return_empty
00119     listitem = _anything_return_empty
00120     definition_list = _anything_return_empty
00121     definition_term = _anything_return_empty
00122     definition_desc = _anything_return_empty
00123     table = _anything_return_empty
00124     table_row = _anything_return_empty
00125     table_cell = _anything_return_empty
00126     _get_bang_args = _anything_return_empty
00127     parser = _anything_return_empty
00128     div = _anything_return_empty
00129     span = _anything_return_empty
00130     escapedText = _anything_return_empty
00131     comment = _anything_return_empty
00132     transclusion = _anything_return_empty
00133 
00134 def macro_TableOfContents(macro, maxdepth=int):
00135     """
00136 Prints a table of contents.
00137 
00138  maxdepth:: maximum depth the table of contents is generated for (defaults to unlimited)
00139     """
00140     try:
00141         mindepth = int(macro.request.getPragma('section-numbers', 1))
00142     except (ValueError, TypeError):
00143         mindepth = 1
00144 
00145     if maxdepth is None:
00146         maxdepth = 99
00147 
00148     pname = macro.formatter.page.page_name
00149 
00150     macro.request.uid_generator.push()
00151 
00152     macro.request._tocfm_collected_headings = []
00153     macro.request._tocfm_orig_formatter = macro.formatter
00154 
00155     tocfm = TOCFormatter(macro.request)
00156     p = Page(macro.request, pname, formatter=tocfm, rev=macro.request.rev)
00157 
00158     # this is so we get a correctly updated TOC if we just preview in the editor -
00159     # the new content is not stored on disk yet, but available as macro.parser.raw:
00160     p.set_raw_body(macro.parser.raw, modified=1)
00161 
00162     output = macro.request.redirectedOutput(p.send_page,
00163                                             content_only=True,
00164                                             count_hit=False,
00165                                             omit_footnotes=True)
00166 
00167     _ = macro.request.getText
00168 
00169     result = [
00170         macro.formatter.div(1, css_class="table-of-contents"),
00171         macro.formatter.paragraph(1, css_class="table-of-contents-heading"),
00172         macro.formatter.text(_('Contents')),
00173         macro.formatter.paragraph(0),
00174     ]
00175 
00176 
00177     # find smallest used level and use that as the outer-most indentation,
00178     # to fix pages like HelpOnMacros that only use h2 and lower levels.
00179     lastlvl = 100
00180     for lvl, id, txt in macro.request._tocfm_collected_headings:
00181         if txt is None:
00182             incl_id = id
00183             continue
00184         if lvl < mindepth or lvl > maxdepth or id is None:
00185             continue
00186         if lvl < lastlvl:
00187             lastlvl = lvl
00188 
00189     # headings are 1-based, lastlvl needs to be one less so that one is closed
00190     lastlvl -= 1
00191 
00192     for lvl, id, txt in macro.request._tocfm_collected_headings:
00193         if txt is None:
00194             incl_id = id
00195             continue
00196         if lvl < mindepth or lvl > maxdepth or id is None:
00197             continue
00198 
00199         # will be reset by pop_unique_ids below
00200         macro.request.include_id = incl_id
00201 
00202         need_li = lastlvl >= lvl
00203         while lastlvl > lvl:
00204             result.extend([
00205                 macro.formatter.listitem(0),
00206                 macro.formatter.number_list(0),
00207             ])
00208             lastlvl -= 1
00209         while lastlvl < lvl:
00210             result.extend([
00211                 macro.formatter.number_list(1),
00212                 macro.formatter.listitem(1),
00213             ])
00214             lastlvl += 1
00215         if need_li:
00216             result.extend([
00217                 macro.formatter.listitem(0),
00218                 macro.formatter.listitem(1),
00219             ])
00220         result.extend([
00221             '\n',
00222             macro.formatter.anchorlink(1, id),
00223             macro.formatter.text(txt),
00224             macro.formatter.anchorlink(0),
00225         ])
00226 
00227     while lastlvl > 0:
00228         result.append(macro.formatter.listitem(0))
00229         result.append(macro.formatter.number_list(0))
00230         lastlvl -= 1
00231 
00232     macro.request.uid_generator.pop()
00233 
00234     result.append(macro.formatter.div(0))
00235     return ''.join(result)