source: trac/trunk/wiki-macros/TracNav.py @ 3054

Revision 3054, 8.2 KB checked in by moschny, 8 years ago (diff)

Rewrite the TOC structure parser, using a recursive algorithm instead
of an explicit stack.

The new method accepts table-of-contents with varying amounts of
indentation characters (unlike the current version of Trac itself,
where deeper levels must be indented by exactly two white spaces).

Furthermore, it "repairs" malformed TOCs by inserting dummy
nodes. Trac does this, too, but may insert more dummy nodes than our
method (because of the fixed amount of whitespace expected per level).

In any case, there shouldn't be any differences between Trac's and our
interpretation of the structure for most "well-formed" TOCs.

Line 
1# -*- coding: utf-8 -*-
2"""
3= TracNav: The navigation bar for Trac =
4
5This macro implements a fully customizable navigation bar for the Trac
6wiki engine. The contents of the navigation bar is a wiki page itself
7and can be edited like any other wiki page through the web
8interface. The navigation bar supports hierarchical ordering of
9topics. The design of TracNav mimics the design of the TracGuideToc
10that was originally supplied with Trac. The drawback of TracGuideToc
11is that it is not customizable without editing its source code and
12that it does not support hierarchical ordering.
13
14
15== Installation ==
16
17To install TracNav, place the file `TracNav.py` in the `wiki-macros`
18subdirectory and the accompanying `tracnav.css` file in the
19`templates` subdirectory of your Trac project. Add this line
20{{{
21@import url(<?cs var:chrome.href ?>/site/tracnav.css);
22}}}
23to the `templates/site_css.cs` file of your Trac project.
24
25The `tracnav.css` file defines the styles for displaying the
26navigation bar. These styles build upon the styles for !TracGuideToc
27that come with your Trac distribution. If you just install the macro
28but miss to install the style file, TracNav will work but look
29somewhat strange.
30
31
32== Usage ==
33
34To use TracNav, create an index page for your site and call the
35TracNav macro on each page, where the navigation bar should be
36displayed. The index page is a regular wiki page. The page with the
37table of contents must include an unordered list of links that should
38be displayed in the navigation bar.
39
40To display the navigation bar on a page, you must call the TracNav
41macro on that page an pass the name of your table of contents as
42argument.
43
44
45== Author and license ==
46
47Copyright 2005 Bernhard Haumacher (haui at haumacher.de)
48
49{{{
50This program is free software; you can redistribute it and/or modify
51it under the terms of the GNU General Public License as published by
52the Free Software Foundation; either version 2 of the License, or
53(at your option) any later version.
54
55This program is distributed in the hope that it will be useful,
56but WITHOUT ANY WARRANTY; without even the implied warranty of
57MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
58GNU General Public License for more details.
59
60You should have received a copy of the GNU General Public License
61along with this program; if not, write to the Free Software
62Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
63}}}
64
65== Additional information and a life example ==
66
67Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav
68"""
69
70import re
71import sys
72from trac.wiki.api import WikiSystem
73from trac.wiki.model import WikiPage
74
75listRule = re.compile(r"""^(?P<indent> *)\* +(?:(?P<wikilink>\[wiki:(?P<link>(&#34;([^&#34;]*)&#34;|'([^']*)')|([^ \]]+)) +(?P<label>[^\]]*)\])|(?P<text>.*))""", re.M)
76
77def getToc(hdf, env, curpage, name):
78    tocText = "* Table of contents"
79
80    preview = hdf.getValue('args.preview', "")
81    if preview and (name == curpage):
82        tocText = hdf.getValue('wiki.page_source', tocText);
83    else:
84        if WikiSystem(env).has_page(name):
85            tocText = WikiPage(env, name).text
86
87    # env.log.debug(tocText)
88    return tocText
89
90
91def getTocEntry(tocText):
92    nextPos = 0
93    while 1:
94        match = listRule.search(tocText, nextPos)
95        if not match:
96            # env.log.debug("No more matches")
97            return
98
99        indent = len(match.group('indent'))
100        if match.group('wikilink'):
101            link = match.group('link')
102            label = match.group('label')
103        else:
104            link = None
105            label = match.group('text')
106
107        # if link == None:
108        #     env.log.debug(label + " ---")
109        # else:
110        #     env.log.debug(label + ": " + link)
111   
112        yield indent, link, label
113        nextPos = match.end()
114
115
116def getTocEntryAndNextIndent(g):
117    indent, link, label = g.next()
118    yield indent
119
120    ready = False
121    while not ready:
122        yield link, label
123        try:
124            indent, link, label = g.next()
125        except StopIteration:
126            indent, ready = -1, True
127        yield indent
128
129
130def _parseToc(g, nextIndent, level = 0):
131    list = []
132    if nextIndent > level:
133        subList, nextIndent = _parseToc(g, nextIndent, level + 1)
134        if nextIndent < level:
135            # level is empty
136            return subList, nextIndent
137        else:
138            # broken indentation structure
139            list.append((None, None, subList))
140    while 1:
141        if nextIndent == level:
142            (link, label), nextIndent = g.next(), g.next()
143            if nextIndent > level:
144                subList, nextIndent = _parseToc(g, nextIndent, level + 1)
145                list.append((link, label, subList))
146            else:
147                list.append((link, label, None))
148        else:
149            assert nextIndent < level
150            return list, nextIndent
151
152
153def parseToc(tocText):
154    g = getTocEntryAndNextIndent(getTocEntry(tocText))
155    list, _ = _parseToc(g, g.next())
156    return list
157   
158
159def execute(hdf, args, env):
160    preview = hdf.getValue('args.preview', "")
161    curpage = hdf.getValue('wiki.page_name', "")
162    name = args
163    if not name:
164        name = 'TOC'
165
166    db = env.get_db_cnx()
167    toc = parseToc(getToc(hdf, env, curpage, name))
168    if not toc:
169        msg = ''
170        msg += '<div class="system-message"><strong>Error: Table of contents does not exist.'
171        if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')):
172            msg += ' Click here to <a href="%s?edit=yes">edit</a>.' % env.href.wiki(name)
173        msg += '</strong></div>\n'
174        return msg
175
176    (found, filtered) = filter(curpage, toc, 0)
177    if found:
178        return displayAll(hdf, env, name, curpage, filtered, 0)
179    else:
180        return displayAll(hdf, env, name, curpage, toc, 0)
181
182
183def filter(curpage, toc, level):
184    found = 0
185    result = []
186    for name, title, sub in toc:
187        if sub == None:
188            if name == curpage:
189                found = 1
190            result.append((name, title, None))
191        else:
192            (subfound, subtoc) = filter(curpage, sub, level + 1)
193            if subfound:
194                found = 1
195            if subfound or (name == None):
196                if level == 0 and name != None:
197                    prepended = [(name, title, subtoc)]
198                    prepended.extend(result)
199                    result = prepended
200                else:
201                    result.append((name, title, subtoc))
202            else:
203                result.append((name, title, []))
204    return (found, result)
205
206def indentation(col):
207    return ' ' * col
208
209def displayAll(hdf, env, name, curpage, toc, col):
210    preview = hdf.getValue('args.preview', "")
211    html = ''
212    html += '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
213    col += 1
214
215    html += '%s<h2><a href="http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav">TracNav</a> menu</h2>' % indentation(col)
216
217    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
218        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % (indentation(col), env.href.wiki(name))
219    html += '%s<ul>\n' % indentation(col)
220    col += 1
221    html += display(env, curpage, toc, 0, col)
222    col -= 1
223    html += '%s</ul>\n' % indentation(col)
224    col -= 1
225    html += '%s</div>\n' % indentation(col)
226    return html
227
228def display(env, curpage, toc, depth, col):
229    html = ''
230    for name, title, sub in toc:
231        liStyle = ' style="padding-left: %dem;"' % (depth + 1)
232        if sub == None:
233            if name == curpage:
234                cls = ' class="active"'
235            else:
236                cls = ''
237            html += '%s<li%s%s>' % (indentation(col), liStyle, cls)
238            if name == None:
239                html += title
240            else:
241                html += '<a href="%s">%s</a>' % (env.href.wiki(name), title)
242            html += '</li>\n'
243        else:
244            html += '%s<li%s>\n' % (indentation(col), liStyle)
245            col += 1
246            if name == None or len(sub) > 0:
247                html += '%s<h4>%s</h4>\n' % (indentation(col), title)
248            else:
249                html += '%s<h4><a href="%s">%s</a>...</h4>\n' % (indentation(col), env.href.wiki(name), title)
250            col -= 1
251            html += '%s</li>\n' % indentation(col)
252            if len(sub) > 0:
253                html += display(env, curpage, sub, depth + 1, col)
254    return html
255
Note: See TracBrowser for help on using the repository browser.