Attachment 'VisualSiteMap.py'
Download 1 """
2 MoinMoin - VisualSiteMap action
3
4 Idea is based on the webdot.py action.
5
6 More or less redid it from scratch. Differs from the webdot action in several ways:
7 * Uses the dot executable, not webdot, since webdot's not available on windows.
8 * All links up to the search depth are displayed. So you get the full map, not only a part of it.
9 * There's no maximal limit to the displayed nodes. Again, I wanted the full map.
10 * Nodes are marked during depth first visit, so each node is visited only once.
11 * The visit method in class LocalSiteMap gets the whole tree as parameter. That way additional treenode information
12 may be shown in the graph.
13 * All edges between nodes contained in the graph are displayed, even if MAX_DEPTH is exceeded that way.
14 * Search depth may be controlled through the command line argument 'depth': ?action=VisualSiteMap&depth=1
15 * Experimental: Optional depth controls (set DEPTH_CONTROL = 1 and give it a try)
16 * Nodes linked more then STRONG_LINK_NR times are highlighted using the STRONG_COLOR
17
18 Add this to your stylesheet:
19 img.sitemap
20 {
21 border-width: 1;
22 border-color: #000000;
23 }
24
25 $Id$
26 """
27
28
29 # Imports
30 import string,sys,re,os;
31 from MoinMoin import config, wikiutil, webapi, user;
32 from MoinMoin.Page import Page;
33
34 # Graph controls
35 DEFAULT_DEPTH = 2;
36 STRONG_LINK_NR = 4;
37 ROOT_URL = "/MyWiki";
38
39 # Experimental: Optional controls for interactive modification of the search depth.
40 DEPTH_CONTROL = 0;
41 MAX_DEPTH = 4;
42
43 # This should be a public path on your web server. The dot files, images and map files are created in this directory and
44 # served from there.
45 CACHE_DIR = "C:/DocumentRoot/cache/";
46 CACHE_URL = "http://MyWiki/cache/";
47
48 # Absolute location of the dot (or neato) executable.
49 DOT_EXE = "C:/Programme/ATT/GraphViz/bin/neato.exe";
50
51 # Colors of boxes and edges.
52 BOX_COLOR ="#E0F0FF";
53 ROOT_COLOR = "#FFE0E0";
54 STRONG_COLOR = "#E0FFE0";
55 EDGE_COLOR ="#888888";
56
57 # Categories are filtered in some way.
58 CATEGORY_STRING = "^Kategorie"
59
60 # Code starts here
61 def execute(pagename, request):
62 _ = request.getText;
63
64 maxdepth = int(DEFAULT_DEPTH);
65 if DEPTH_CONTROL and request.form.has_key('depth'):
66 maxdepth = int(request.form['depth'].value);
67
68 if int(maxdepth) > int(MAX_DEPTH):
69 maxdepth = MAX_DEPTH;
70
71 webapi.http_headers(request);
72 wikiutil.send_title(request, _('Visual Map of %s') % (pagename), pagename=pagename);
73
74 wikiname = wikiutil.quoteWikiname(pagename);
75 dotfilename = '%s/%s.dot' % (CACHE_DIR, wikiname);
76 pngfilename = '%s/%s.png' % (CACHE_DIR, wikiname);
77 pngurl = '%s/%s.png' % (CACHE_URL, wikiname);
78 mapfilename = '%s/%s.cmap' % (CACHE_DIR, wikiname);
79
80 dotfile = open(dotfilename,'w');
81
82 dotfile.write('digraph G {\n');
83 dotfile.write(' URL="%s";\n' % wikiname);
84 dotfile.write(' ratio=compress;\n');
85 dotfile.write(' overlap=false;\n');
86 dotfile.write(' concentrate=true;\n');
87 dotfile.write(' edge [color="%s"];\n' % EDGE_COLOR);
88 dotfile.write(' node [URL="%s/\N", ' % ROOT_URL);
89 dotfile.write('fontcolor=black, fontname=%s , fontsize=%s, style=filled, color="%s"]\n' % ("arial","8", BOX_COLOR));
90 dotfile.write(LocalSiteMap(pagename, maxdepth).output(request));
91 dotfile.write('}\n');
92 dotfile.close();
93
94 os.system('%s -Tpng -o%s %s' % (DOT_EXE, pngfilename, dotfilename));
95 os.system('%s -Tcmap -o%s %s' % (DOT_EXE, mapfilename, dotfilename));
96
97 print '<center><img class="sitemap" border=1 src="%s" usemap="#map1"></center>' % (pngurl);
98 print '<map name="map1">';
99 mapfile = open(mapfilename,'r');
100 for row in mapfile.readlines():
101 print row;
102 mapfile.close();
103
104 print '</map>';
105
106 if DEPTH_CONTROL:
107 print '<p align="center">';
108 if maxdepth > 1:
109 print '<a href="%s/%s?action=VisualSiteMap&depth=%s">Less</a>' % (ROOT_URL, pagename, maxdepth-1);
110 else:
111 print 'Less';
112
113 print ' | ';
114
115 if maxdepth < MAX_DEPTH:
116 print '<a href="%s/%s?action=VisualSiteMap&depth=%s">More</a>' % (ROOT_URL, pagename, maxdepth+1);
117 else:
118 print 'More';
119 print '</p>';
120
121 print '<p align="center"><small>Search depth is %s. Nodes linked more than %s times are highlighted.</small></p>' % (maxdepth, STRONG_LINK_NR);
122
123 wikiutil.send_footer(request, pagename);
124
125 sys.exit(0);
126
127 class LocalSiteMap:
128 def __init__(self, name, maxdepth):
129 self.name = name;
130 self.result = [];
131 self.maxdepth = maxdepth;
132
133 def output(self, request):
134 pagebuilder = GraphBuilder(request, self.maxdepth);
135 root = pagebuilder.build_graph(self.name);
136 # write nodes
137 for node in pagebuilder.all_nodes:
138 uname=unicode(node.name, config.charset).encode('UTF-8');
139 self.append(' %s'% wikiutil.quoteWikiname(node.name));
140 if node.depth > 0:
141 if node.linkedto >= STRONG_LINK_NR:
142 self.append(' [label="%s",color="%s"];\n' % (uname, STRONG_COLOR));
143 else:
144 self.append(' [label="%s"];\n' % (uname));
145
146 else:
147 self.append('[label="%s",shape=box,style=filled,color="%s"];\n' % (uname, ROOT_COLOR));
148 # write edges
149 for edge in pagebuilder.all_edges:
150 self.append(' %s->%s;\n' % (wikiutil.quoteWikiname(edge[0].name),wikiutil.quoteWikiname(edge[1].name)));
151
152 return string.join(self.result, '');
153
154 def append(self, text):
155 self.result.append(text);
156
157
158 class GraphBuilder:
159
160 def __init__(self, request, maxdepth):
161 self.request = request;
162 self.maxdepth = maxdepth;
163 self.all_nodes = [];
164 self.all_edges = [];
165
166 def is_ok(self, child):
167 if not self.request.user.may.read(child):
168 return 0;
169 if Page(child).exists() and (not re.search(r'%s' % CATEGORY_STRING,child)):
170 return 1;
171 return 0;
172
173 def build_graph(self, name):
174 # Reuse generated trees
175 nodesMap = {};
176 root = Node(name);
177 nodesMap[name] = root;
178 root.visited = 1;
179 self.all_nodes.append(root);
180 self.recurse_build([root], 1, nodesMap);
181 return root;
182
183 def recurse_build(self, nodes, depth, nodesMap):
184 # collect all nodes of the current search depth here for the next recursion step
185 child_nodes = [];
186 # iterate over the nodes
187 for node in nodes:
188 # print "<h2>Kids of %s</h2>" % node.name;
189 for child in Page(node.name).getPageLinks(self.request):
190 if self.is_ok(child):
191 # print "Child %s" % child;
192 # Create the node with the given name
193 if not nodesMap.has_key(child):
194 # create the new node and store it
195 newNode = Node(child);
196 newNode.depth = depth;
197 # print "is new";
198 else:
199 newNode = nodesMap[child];
200 # print "is old";
201 # print ". <br>";
202 # If the current depth doesn't exceed the maximum depth, add newNode to recursion step
203 if (int(depth) <= int(self.maxdepth)):
204 # The node is appended to the nodes list for the next recursion step.
205 nodesMap[child] = newNode;
206 self.all_nodes.append(newNode);
207 child_nodes.append(newNode);
208 node.append(newNode);
209 # Draw an edge.
210 self.all_edges.append((node, newNode));
211 newNode.linkedto += 1;
212 # recurse, if the current recursion step yields children
213 if len(child_nodes):
214 self.recurse_build(child_nodes, depth+1, nodesMap);
215
216 class Node:
217 def __init__(self, name):
218 self.name = name;
219 self.children = [];
220 self.visited = 0;
221 self.linkedfrom = 0;
222 self.linkedto = 0;
223 self.depth = 0;
224
225 def append(self, node):
226 self.children.append(node);
227 self.linkedfrom += 1;
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.